{
  "openapi": "3.1.0",
  "info": {
    "title": "Parky API (Admin Portal)",
    "version": "0.1.0",
    "description": "Parky API — 管理者ポータル (dev-admin.parky.co.jp) が叩くエンドポイント。管理者認証 + RBAC で保護。"
  },
  "servers": [
    {
      "url": "https://api.parky.co.jp",
      "description": "Production"
    },
    {
      "url": "https://stg-api.parky.co.jp",
      "description": "Staging"
    },
    {
      "url": "https://dev-api.parky.co.jp",
      "description": "Development"
    },
    {
      "url": "http://localhost:8787",
      "description": "Local"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Supabase Auth が発行した JWT（HS256 / SUPABASE_JWT_SECRET 署名）"
      }
    },
    "schemas": {
      "AppleAppSiteAssociation": {
        "type": "object",
        "properties": {
          "applinks": {
            "type": "object",
            "properties": {
              "details": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "appIDs": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "components": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "/": {
                            "type": "string"
                          },
                          "comment": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "/"
                        ]
                      }
                    }
                  },
                  "required": [
                    "appIDs",
                    "components"
                  ]
                }
              }
            },
            "required": [
              "details"
            ]
          },
          "webcredentials": {
            "type": "object",
            "properties": {
              "apps": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "apps"
            ]
          }
        },
        "required": [
          "applinks",
          "webcredentials"
        ]
      },
      "AssetLinksEntry": {
        "type": "object",
        "properties": {
          "relation": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "target": {
            "type": "object",
            "properties": {
              "namespace": {
                "type": "string"
              },
              "package_name": {
                "type": "string"
              },
              "sha256_cert_fingerprints": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "namespace",
              "package_name",
              "sha256_cert_fingerprints"
            ]
          }
        },
        "required": [
          "relation",
          "target"
        ]
      },
      "AssetLinksResponse": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/AssetLinksEntry"
        }
      },
      "Admin": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "role_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "last_login_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "name",
          "email",
          "role_id",
          "status",
          "last_login_at",
          "created_at"
        ]
      },
      "AdminMe": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "role_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "last_login_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "name",
          "email",
          "role_id",
          "status",
          "last_login_at",
          "created_at"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "not_found"
                ]
              },
              "message": {
                "type": "string",
                "examples": [
                  "Not Found"
                ]
              },
              "message_key": {
                "type": "string",
                "examples": [
                  "common.error.not_found"
                ]
              },
              "request_id": {
                "type": "string",
                "examples": [
                  "7d4e5…-…"
                ]
              },
              "param": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "リクエスト上のエラー対象フィールド名（zod path[0] 等）。root レベルエラーは null。",
                "examples": [
                  "email"
                ]
              },
              "doc_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "エラーコードに対応するドキュメント URL。未整備時は null。",
                "examples": [
                  null
                ]
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "AdminNotificationPrefs": {
        "type": "object",
        "properties": {
          "new_owner": {
            "type": "boolean",
            "default": true
          },
          "new_parking": {
            "type": "boolean",
            "default": true
          },
          "sales_daily": {
            "type": "boolean",
            "default": true
          },
          "sales_monthly": {
            "type": "boolean",
            "default": true
          },
          "system_alert": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "Role": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "role_code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "is_system": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "role_code",
          "name",
          "description",
          "color",
          "is_system",
          "created_at"
        ]
      },
      "GeometryPoint": {
        "type": "object",
        "properties": {
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "description": "緯度（WGS84, 10進）。",
            "examples": [
              35.669277
            ]
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "description": "経度（WGS84, 10進）。",
            "examples": [
              139.758991
            ]
          }
        },
        "required": [
          "lat",
          "lng"
        ],
        "additionalProperties": false
      },
      "GeoJSONLineString": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "LineString"
            ]
          },
          "coordinates": {
            "type": "array",
            "items": {
              "type": "array",
              "prefixItems": [
                {
                  "type": "number",
                  "minimum": -180,
                  "maximum": 180
                },
                {
                  "type": "number",
                  "minimum": -90,
                  "maximum": 90
                }
              ],
              "description": "[lng, lat] のペア（GeoJSON 仕様、経度が先）",
              "examples": [
                [
                  139.758991,
                  35.669277
                ]
              ]
            },
            "minItems": 2,
            "description": "2 点以上の座標列。",
            "examples": [
              [
                [
                  139.758,
                  35.669
                ],
                [
                  139.759,
                  35.67
                ]
              ]
            ]
          }
        },
        "required": [
          "type",
          "coordinates"
        ],
        "additionalProperties": false
      },
      "GeoJSONPolygon": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Polygon"
            ]
          },
          "coordinates": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "array",
                "prefixItems": [
                  {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  },
                  {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  }
                ],
                "description": "[lng, lat] のペア（GeoJSON 仕様、経度が先）",
                "examples": [
                  [
                    139.758991,
                    35.669277
                  ]
                ]
              },
              "minItems": 4
            },
            "minItems": 1,
            "description": "外周 + 内周（穴）の配列。各リングは最低 4 点、最後の点は最初の点と一致すること（閉じたリング）。",
            "examples": [
              [
                [
                  [
                    139.758,
                    35.669
                  ],
                  [
                    139.759,
                    35.669
                  ],
                  [
                    139.759,
                    35.67
                  ],
                  [
                    139.758,
                    35.67
                  ],
                  [
                    139.758,
                    35.669
                  ]
                ]
              ]
            ]
          }
        },
        "required": [
          "type",
          "coordinates"
        ],
        "additionalProperties": false
      },
      "ParkingLotGeometry": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "shape_type": {
                "type": "string",
                "enum": [
                  "point"
                ]
              },
              "point": {
                "$ref": "#/components/schemas/GeometryPoint"
              }
            },
            "required": [
              "shape_type",
              "point"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "shape_type": {
                "type": "string",
                "enum": [
                  "line"
                ]
              },
              "line": {
                "$ref": "#/components/schemas/GeoJSONLineString"
              }
            },
            "required": [
              "shape_type",
              "line"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "shape_type": {
                "type": "string",
                "enum": [
                  "area"
                ]
              },
              "area": {
                "$ref": "#/components/schemas/GeoJSONPolygon"
              }
            },
            "required": [
              "shape_type",
              "area"
            ],
            "additionalProperties": false
          },
          {
            "type": "null"
          }
        ],
        "description": "駐車場の座標 / 形状（shape_type + 座標）。座標未設定の場合は `null`。flat の lat/lng/shape_type/area は返さない（すべてこの nested object に集約）。"
      },
      "ParkingLotResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "作成 or 更新された駐車場 UUID",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "name": {
            "type": "string",
            "description": "駐車場の表示名"
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "description": "住所全文。未設定なら null。"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "active",
              "on_hold",
              "withdrawn",
              "rejected"
            ],
            "description": "公開状態。owner POST 経由は常に `pending`、admin は body 指定値。",
            "examples": [
              "active"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "manual",
              "s-park",
              "times",
              "repark",
              "navipark",
              "p-colle"
            ],
            "description": "データ起源（manual / s-park / times / ...）。codes.category_id=`lot_source`。",
            "examples": [
              "manual"
            ]
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "親駐車場 UUID（ブランド系列の名寄せ用）。通常 null。",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "variant_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "同一系列内の区別ラベル（「第1」「第2」「裏口」等）。"
          },
          "place_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Google Place ID（archive/032）。Pコレ取り込みの冪等キー。"
          },
          "total_spaces": {
            "type": [
              "integer",
              "null"
            ],
            "description": "駐車可能台数。非負整数 or null。"
          },
          "structure": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "flat",
              "self_multi",
              "mechanical"
            ],
            "description": "駐車場構造（codes.category_id=`lot_structure`）。",
            "examples": [
              "mechanical"
            ]
          },
          "entry_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "self",
              "mechanical"
            ],
            "description": "入庫方式（codes.category_id=`lot_entry`）。",
            "examples": [
              "mechanical"
            ]
          },
          "entry_difficulty": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "easy",
              "normal",
              "hard"
            ],
            "description": "入庫難易度（codes.category_id=`entry_difficulty`）。",
            "examples": [
              "easy"
            ]
          },
          "max_height_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "車高制限(m)。"
          },
          "max_width_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "車幅制限(m)。"
          },
          "max_length_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "全長制限(m)。"
          },
          "max_weight_t": {
            "type": [
              "number",
              "null"
            ],
            "description": "重量制限(t)。"
          },
          "min_clearance_cm": {
            "type": [
              "integer",
              "null"
            ],
            "description": "最低地上高(cm)。"
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "description": "タイヤ幅制限(mm)。null = 制限なし。archive/041。"
          },
          "max_parking_duration_min": {
            "type": [
              "integer",
              "null"
            ],
            "description": "最大駐車時間(分)。archive/015。"
          },
          "receipt_available": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "領収書発行可否。DEFAULT `false`。"
          },
          "operator_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "paraca",
              "times",
              "repark",
              "navipark",
              "mitsui",
              "npc24h",
              "ecoloparc",
              "coinpark",
              "other"
            ],
            "description": "運営会社コード（codes.category_id=`operator`）。",
            "examples": [
              "repark"
            ]
          },
          "raw_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "取り込み元の生テキスト（CSV / スクレイピング原文）。通常 null。"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "作成日時（UTC, ISO 8601）。",
            "examples": [
              "2026-04-17T12:00:00Z"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "最終更新日時（UTC, ISO 8601）。20260421150000 trigger で自動更新。",
            "examples": [
              "2026-04-17T12:00:00Z"
            ]
          },
          "geometry": {
            "$ref": "#/components/schemas/ParkingLotGeometry"
          }
        },
        "required": [
          "id",
          "name",
          "address",
          "status",
          "source",
          "parent_id",
          "variant_label",
          "place_id",
          "total_spaces",
          "structure",
          "entry_method",
          "entry_difficulty",
          "max_height_m",
          "max_width_m",
          "max_length_m",
          "max_weight_t",
          "min_clearance_cm",
          "max_tire_width_mm",
          "max_parking_duration_min",
          "receipt_available",
          "operator_code",
          "raw_text",
          "created_at",
          "updated_at",
          "geometry"
        ],
        "additionalProperties": false,
        "description": "fetchLot() が返す parking_lots 行の全列（座標/形状は `geometry` nest に集約）。passthrough は使わず全カラムを明示する。"
      },
      "ParkingLotBundleResponse": {
        "type": "object",
        "properties": {
          "parking_lot": {
            "$ref": "#/components/schemas/ParkingLotResponse"
          },
          "hours_count": {
            "type": "integer",
            "description": "parking_lot_hours に INSERT した行数。bundle body に hours を含めない場合は 0。",
            "examples": [
              1
            ]
          },
          "pricing_rules_count": {
            "type": "integer",
            "description": "parking_lot_pricing_rules に INSERT した行数。",
            "examples": [
              2
            ]
          },
          "images_count": {
            "type": "integer",
            "description": "parking_lot_images に INSERT（create 時）/ 差分適用（update 時）した件数。",
            "examples": [
              2
            ]
          },
          "tags_count": {
            "type": "integer",
            "description": "parking_lot_tags に UPSERT した件数（state=null の DELETE も含む）。",
            "examples": [
              3
            ]
          },
          "owners_count": {
            "type": "integer",
            "description": "parking_lot_owners に INSERT した件数。admin 版は body.owner_ids の長さ、owner セルフ登録版は常に 1（自動紐付け）。",
            "examples": [
              1
            ]
          }
        },
        "required": [
          "parking_lot",
          "hours_count",
          "pricing_rules_count",
          "images_count",
          "tags_count",
          "owners_count"
        ]
      },
      "EntryExitPin": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "entry",
              "exit",
              "both"
            ],
            "description": "ピン種別。`entry`=入口 / `exit`=出口 / `both`=入出口兼用。",
            "examples": [
              "both"
            ]
          },
          "lat": {
            "type": "number",
            "description": "緯度。",
            "examples": [
              35.6587
            ]
          },
          "lng": {
            "type": "number",
            "description": "経度。",
            "examples": [
              139.7454
            ]
          },
          "label": {
            "type": "string",
            "maxLength": 50,
            "description": "ピンのラベル（「北側ゲート」等）。省略可。",
            "examples": [
              "北側ゲート"
            ]
          }
        },
        "required": [
          "kind",
          "lat",
          "lng"
        ],
        "additionalProperties": false
      },
      "EntryExitLocations": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "shared",
              "separate",
              "multiple"
            ],
            "description": "出入口モード。\n- `shared`   : 入出口が同一（1 か所兼用）\n- `separate` : 入口と出口が別（各 1 か所）\n- `multiple` : 複数出入口あり（pins に個別記録）",
            "examples": [
              "shared"
            ]
          },
          "pins": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EntryExitPin"
            },
            "maxItems": 20,
            "description": "出入口ピン一覧。`mode` に応じた件数を格納する。最大 20 件。"
          }
        },
        "required": [
          "mode",
          "pins"
        ],
        "additionalProperties": false,
        "description": "出入口ピン情報。`entry_exit_locations` field_value に JSONB で保存される。省略時 null。"
      },
      "ParkingLotCoreCreate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "駐車場の表示名（必須）。系列名＋地名の形式が多い。",
            "examples": [
              "晴海西口第一駐車場"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "住所全文。都道府県 + 市区 + 町丁目 + 番地。公開検索の ILIKE 対象。",
            "examples": [
              "東京都中央区晴海20-39"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "active",
              "on_hold",
              "withdrawn",
              "rejected"
            ],
            "description": "駐車場の公開状態 (CHECK 制約付き)。\n- `draft`     : 下書き（Owner が記入中。admin には未通知）\n- `pending`   : 申請中（admin 審査待ち）\n- `active`    : 公開中（search 結果に出る）\n- `on_hold`   : 保留中\n- `withdrawn` : 取下げ\n- `rejected`  : 却下（admin が審査で却下）\nOwner Bundle API では `draft` / `pending` の 2 値のみ受領。`active` 以降の遷移は admin 側 API。",
            "examples": [
              "active"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "manual",
              "s-park",
              "times",
              "repark",
              "navipark",
              "p-colle"
            ],
            "description": "データ起源（codes.category_id=`lot_source`）。\n- `manual`   : 手動登録（admin ポータル / owner セルフ登録のデフォルト）\n- `s-park`   : S-PARK（運営会社 S-PARK のスクレイピング）\n- `times`    : タイムズ（タイムズ 24 のスクレイピング）\n- `repark`   : リパーク（三井のリパーク系列のスクレイピング）\n- `navipark` : ナビパーク\n- `p-colle`  : P-Colle（駐車場情報収集隊の取り込み）",
            "examples": [
              "manual"
            ]
          },
          "lot_type": {
            "type": "string",
            "enum": [
              "regular",
              "meter",
              "ticket",
              "monthly_only",
              "hourly_with_monthly",
              "prepaid_coin"
            ],
            "description": "駐車場種別 (default `regular`)。`meter` / `ticket` を選んだ場合は wizard が物理制限・spots step をスキップする (§17)。",
            "examples": [
              "regular"
            ]
          },
          "vehicle_type_max": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50,
            "description": "lot レベル default の最大車種 (codes.category_id=`vehicle_type`)。spot レベルで上書き可能。NULL = 未設定。",
            "examples": [
              "minivan"
            ]
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "親駐車場 UUID（ブランド系列の名寄せ用）。通常 null。",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "variant_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "同一系列内の区別ラベル（「第1」「第2」「裏口」等）。",
            "examples": [
              "第1"
            ]
          },
          "place_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Google Place ID（archive/032）。Pコレ取り込みの冪等キー兼用で UNIQUE 制約あり。単発登録では null で OK。",
            "examples": [
              "ChIJN1t_tDeuEmsRUsoyG83frY4"
            ]
          },
          "total_spaces": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "駐車可能台数。非負整数。",
            "examples": [
              12
            ]
          },
          "structure": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "flat",
              "self_multi",
              "mechanical"
            ],
            "description": "駐車場構造（codes.category_id=`lot_structure`）。\n- `flat`       : 平置き（屋根なし / 青空）\n- `self_multi` : 自走式立体（自走で各階へ）\n- `mechanical` : 機械式立体（パレット入れ替え）",
            "examples": [
              "mechanical"
            ]
          },
          "entry_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "self",
              "mechanical"
            ],
            "description": "入庫方式（codes.category_id=`lot_entry`）。\n- `self`       : 自走式（自分で停める）\n- `mechanical` : 機械式（パレットに車を載せる）\n`structure=mechanical` なら通常 `entry_method=mechanical`、\n`structure=flat`/`self_multi` なら `entry_method=self` が一般的。",
            "examples": [
              "mechanical"
            ]
          },
          "entry_difficulty": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "easy",
              "normal",
              "hard"
            ],
            "description": "入庫難易度（codes.category_id=`entry_difficulty`）。\n- `easy`   : 簡単\n- `normal` : 普通\n- `hard`   : 難しい",
            "examples": [
              "easy"
            ]
          },
          "max_height_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "車高制限(m)。機械式は 1.55/2.10、自走式は 2.10/2.50 が典型。",
            "examples": [
              1.55
            ]
          },
          "max_width_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "車幅制限(m)。1.85 / 1.95 が典型。",
            "examples": [
              1.95
            ]
          },
          "max_length_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "全長制限(m)。5.00 / 5.30 が典型。",
            "examples": [
              5.3
            ]
          },
          "max_weight_t": {
            "type": [
              "number",
              "null"
            ],
            "description": "重量制限(t)。2.0 / 2.5 が典型。",
            "examples": [
              2.5
            ]
          },
          "min_clearance_cm": {
            "type": [
              "integer",
              "null"
            ],
            "description": "最低地上高(cm)。機械式で特に重要。",
            "examples": [
              10
            ]
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "description": "タイヤ幅制限(mm)。機械式で設定されることが多い。null = 制限なし（検索では合致扱い）。archive/041 で追加。",
            "examples": [
              205
            ]
          },
          "max_parking_duration_min": {
            "type": [
              "integer",
              "null"
            ],
            "description": "最大駐車時間(分)。短時間のみ等の制限がある場合のみ設定。archive/015 で追加。例: `180` = 3 時間。",
            "examples": [
              180
            ]
          },
          "receipt_available": {
            "type": "boolean",
            "description": "領収書発行可否。DEFAULT `false`。",
            "examples": [
              true
            ]
          },
          "operator_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "paraca",
              "times",
              "repark",
              "navipark",
              "mitsui",
              "npc24h",
              "ecoloparc",
              "coinpark",
              "other"
            ],
            "description": "運営会社コード（codes.category_id=`operator`）。\n- `paraca`    : パラカ\n- `times`     : タイムズ24\n- `repark`    : リパーク（三井のリパーク系列）\n- `navipark`  : ナビパーク\n- `mitsui`    : 三井のリパーク（本体）\n- `npc24h`    : NPC24H\n- `ecoloparc` : エコロパーク\n- `coinpark`  : コインパーク\n- `other`     : その他（分類不能 / 個人オーナー等）\n\nowner 自営の駐車場で運営会社が無い場合は `null` で OK。\n新規運営会社は codes マスター追加と schema enum 追記の 2 step で拡張する。",
            "examples": [
              "repark"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "駐車場の説明文（オーナーが自由記述）。UI の詳細ページに表示される。",
            "examples": [
              "駅から徒歩3分の好立地。夜間入庫可能。"
            ]
          },
          "pricing_notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000,
            "description": "料金に関する補足・備考（フリーテキスト）。特典・キャンペーン等の記述に使用。",
            "examples": [
              "月極契約は別途お問い合わせください。"
            ]
          },
          "contact_phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "連絡先電話番号。最大 20 文字。",
            "examples": [
              "03-1234-5678"
            ]
          },
          "contact_email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "format": "email",
            "description": "連絡先メールアドレス。RFC 5321 準拠。最大 200 文字。",
            "examples": [
              "parking@example.com"
            ]
          },
          "contact_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "format": "uri",
            "description": "連絡先 URL（問い合わせフォーム等）。最大 500 文字。",
            "examples": [
              "https://example.com/contact"
            ]
          },
          "capacity_kei": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "軽自動車専用枠数。0〜9999 の整数。",
            "examples": [
              5
            ]
          },
          "capacity_regular": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "普通車枠数。0〜9999 の整数。",
            "examples": [
              20
            ]
          },
          "capacity_oversized": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "大型車枠数。0〜9999 の整数。",
            "examples": [
              2
            ]
          },
          "capacity_motorcycle": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "バイク専用枠数。0〜9999 の整数。",
            "examples": [
              3
            ]
          },
          "capacity_ev": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "EV 充電器付き枠数。0〜9999 の整数。",
            "examples": [
              4
            ]
          },
          "capacity_disabled": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "車椅子（障がい者）専用枠数。0〜9999 の整数。",
            "examples": [
              2
            ]
          },
          "raw_text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000,
            "description": "取り込み元の生テキスト（CSV / スクレイピングの原文）。Pコレ import が JSON 化して格納。UI からは通常送らない（null で OK）。",
            "examples": [
              null
            ]
          },
          "entry_exit_locations": {
            "$ref": "#/components/schemas/EntryExitLocations"
          },
          "geometry": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ParkingLotGeometry"
              },
              {
                "description": "駐車場の座標 / 形状（shape_type と座標をペアで持つ nested object）。Create では省略時 `null`（座標未設定）。Patch では省略=触らない、`null`=座標クリア、object 指定で置換。"
              }
            ]
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "description": "駐車場本体の列。`name` のみ必須。"
      },
      "ParkingLotHourInput": {
        "type": "object",
        "properties": {
          "window_type": {
            "type": "string",
            "enum": [
              "business",
              "entry",
              "exit",
              "after_hours_exit"
            ],
            "description": "時間窓の種別。省略時は `business`（DB DEFAULT と同じ）。2026-04-24 追加カラム。",
            "examples": [
              "business"
            ]
          },
          "day_type": {
            "type": "string",
            "enum": [
              "all",
              "weekday",
              "saturday",
              "sunday",
              "holiday",
              "holiday_eve"
            ],
            "description": "曜日タイプ（parking_lot_hours / parking_lot_pricing_rules 共通）。\ncodes(day_type) 参照。2026-04-24 で 6 コードに整理された。\n- `all`         : 全曜日共通（DEFAULT）\n- `weekday`     : 平日（月〜金）\n- `saturday`    : 土曜\n- `sunday`      : 日曜\n- `holiday`     : 祝日（jp_holidays 参照）\n- `holiday_eve` : 祝前日\n`day_of_week` (0=日〜6=土) と併用して曜日単位の細かい設定も可能。",
            "examples": [
              "all"
            ]
          },
          "day_of_week": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 6,
            "description": "曜日インデックス（0=日曜 / 1=月 / 2=火 / 3=水 / 4=木 / 5=金 / 6=土）。\nnull = 毎日（`day_type='all'` と同義）。\n`day_type='weekday'` + `day_of_week=1` のように併用して「月曜だけの特別時間」を表現も可。",
            "examples": [
              1
            ]
          },
          "is_24h": {
            "type": "boolean",
            "description": "24時間営業フラグ。true の場合 open_time/close_time は無視される。",
            "examples": [
              true
            ]
          },
          "is_closed": {
            "type": "boolean",
            "description": "定休フラグ。true の場合 終日 closed として扱う（open_time/close_time/is_24h より優先）。",
            "examples": [
              false
            ]
          },
          "open_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$",
            "description": "開場時刻 `HH:MM`（24時間制）。`is_24h=true` / `is_closed=true` の場合は null。",
            "examples": [
              "07:00"
            ]
          },
          "close_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$",
            "description": "閉場時刻 `HH:MM`。`24:00` 表記を許容（RPC が 24:00:00 = 翌日 0 時として解釈）。深夜跨ぎは `22:00` → `06:00` のような逆転値で表現（`open_time > close_time` を許容）。",
            "examples": [
              "23:00"
            ]
          },
          "effective_from": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "適用開始日（ISO 8601 date `YYYY-MM-DD`）。null = 常時。季節料金 / 期間限定営業時間に使用。",
            "examples": [
              "2026-04-01"
            ]
          },
          "effective_to": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "適用終了日（ISO 8601 date）。null = 常時。",
            "examples": [
              "2026-09-30"
            ]
          }
        },
        "additionalProperties": false
      },
      "ParkingLotPricingRuleInput": {
        "type": "object",
        "properties": {
          "rule_order": {
            "type": "integer",
            "minimum": 0,
            "description": "表示順・評価順（昇順）。省略時はリクエスト配列のインデックス（0 はじまり）が採用される。同一 lot 内で重複しないこと。",
            "examples": [
              1
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "unit",
              "cap",
              "hourly"
            ],
            "description": "料金ルール種別。\n- `unit`   : 従量（時間単位で課金。`per_minutes` + `price_minor` で表現。例: 30分 200円）\n- `cap`    : 最大料金 / 打止め（`cap_type` + `cap_duration_hours` + `cap_price_minor` で表現。例: 24時間最大 1800円）\n- `hourly` : 集計用の代表時間単価（内部 MV 用途。UI 編集での利用は稀）",
            "examples": [
              "unit"
            ]
          },
          "day_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "all",
              "weekday",
              "saturday",
              "sunday",
              "holiday",
              "holiday_eve"
            ],
            "description": "曜日タイプ（parking_lot_hours / parking_lot_pricing_rules 共通）。\ncodes(day_type) 参照。2026-04-24 で 6 コードに整理された。\n- `all`         : 全曜日共通（DEFAULT）\n- `weekday`     : 平日（月〜金）\n- `saturday`    : 土曜\n- `sunday`      : 日曜\n- `holiday`     : 祝日（jp_holidays 参照）\n- `holiday_eve` : 祝前日\n`day_of_week` (0=日〜6=土) と併用して曜日単位の細かい設定も可能。",
            "examples": [
              "all"
            ]
          },
          "time_start": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$",
            "description": "適用開始時刻 `HH:MM`（`category=unit` で使用）。深夜跨ぎ可（例: `22:00` → `08:00` の逆転値）。",
            "examples": [
              "08:00"
            ]
          },
          "time_end": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$",
            "description": "適用終了時刻 `HH:MM`。",
            "examples": [
              "22:00"
            ]
          },
          "per_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "課金単位（分）。`category=unit` で使用。典型値: 15 / 20 / 30 / 60。`per_minutes=30` + `price_minor=200` で「30分ごとに 200 円」。",
            "examples": [
              30
            ]
          },
          "price_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "課金単価（JPY 最小単位 = 1 円）。`category=unit` で使用。例: `200` = 200 円。`_minor` 命名は 20260421110000_money_columns_minor_rename.sql で統一済み（bigint, 非負整数）。",
            "examples": [
              200
            ]
          },
          "cap_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "daily_max",
              "hour_window_max",
              "duration",
              "daily"
            ],
            "description": "最大料金の適用種別（`category=cap` の時のみ使用）。\n- `daily_max`       : 当日中の最大料金 (新規データはこれを推奨)\n- `hour_window_max` : 指定時間窓内の最大料金。`cap_duration_hours` と組み合わせる\n- `duration`        : (legacy) 指定時間内定額。新規は `hour_window_max` を使用\n- `daily`           : (legacy) 当日中定額。新規は `daily_max` を使用",
            "examples": [
              "daily_max"
            ]
          },
          "cap_duration_hours": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "最大料金の時間窓（`category=cap` + `cap_type=duration` で使用）。典型値: 24（= 24時間以内定額）。",
            "examples": [
              24
            ]
          },
          "cap_price_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "最大料金の金額（JPY 最小単位 = 1 円）。例: `1800` = 1800 円。",
            "examples": [
              1800
            ]
          },
          "cap_repeat": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "最大料金を繰り返し適用するか（24時間毎に打止め再計算する等）。DEFAULT `true`。`false` なら初回 24時間で打止めた後は unit 料金のみ。",
            "examples": [
              true
            ]
          },
          "cap_scope": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50,
            "description": "最大料金の適用範囲ラベル（任意のスコープ識別子）。複数 cap ルールを区別する用途（例: `day` / `night` / `weekend`）。通常 null で OK。",
            "examples": [
              null
            ]
          }
        },
        "additionalProperties": false
      },
      "ParkingLotImageInput": {
        "type": "object",
        "properties": {
          "asset_id": {
            "type": "string",
            "format": "uuid",
            "description": "`assets` テーブルに事前登録済みの画像 UUID。`POST /v1/storage/upload-url` で発行 → PUT で実体 upload → その asset_id をここで紐付ける 2 段階フロー。",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "is_main": {
            "type": "boolean",
            "description": "メイン画像フラグ（一覧カード / サムネイル用）。同 lot で複数 true を付けた場合の整合性はこの API では保証しない（UI 側で 1 枚に絞ってから送ること）。DEFAULT `false`。",
            "examples": [
              true
            ]
          },
          "sort_order": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ギャラリー表示順（昇順）。null は最後尾扱い。",
            "examples": [
              1
            ]
          }
        },
        "required": [
          "asset_id"
        ],
        "additionalProperties": false
      },
      "ParkingLotTagInput": {
        "type": "object",
        "properties": {
          "tag_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[a-z][a-z0-9_]*$",
            "description": "タグを一意に識別するコード値。`tags.slug` を参照する snake_case 文字列。\n現時点でサポートしている値は OAS 例 / `GET /v1/tags` のレスポンスを参照。\n未知の code を送ると 400 `unknown_tag_code` で拒否される。",
            "examples": [
              "ev_charging"
            ]
          },
          "state": {
            "type": "boolean",
            "description": "タグ状態。`true` = 該当 / `false` = 明示的に非該当 / 省略 = `true` 扱い。「不明」に戻したい時は PATCH で `state=null` を送る（DELETE 扱い）。",
            "examples": [
              true
            ]
          }
        },
        "required": [
          "tag_code"
        ],
        "additionalProperties": false
      },
      "ParkingLotBundleCreateBody": {
        "type": "object",
        "properties": {
          "parking_lot": {
            "$ref": "#/components/schemas/ParkingLotCoreCreate"
          },
          "hours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotHourInput"
            },
            "maxItems": 50,
            "description": "営業時間行（parking_lot_hours）。省略時は INSERT しない。1 lot につき最大 50 行（曜日 × 期間のバリエーション考慮）。"
          },
          "pricing_rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotPricingRuleInput"
            },
            "maxItems": 100,
            "description": "料金ルール（parking_lot_pricing_rules）。省略時は INSERT しない。`unit`（時間単位の従量）と `cap`（最大料金）の組合せで料金体系を表現。"
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotImageInput"
            },
            "maxItems": 50,
            "description": "画像紐付け（parking_lot_images）。asset_id は事前に `POST /v1/storage/upload-url` でアップロード済みのもの。"
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotTagInput"
            },
            "maxItems": 100,
            "description": "タグ紐付け（parking_lot_tags）。各要素は `{tag_code, state?}` で、\n`tag_code` は `tags.slug` を指すコード値 (例: `ev_charging`, `covered`, `open_24h`)。\nHandler 側で code → id を一括解決するため、未知の code があれば 400 `unknown_tag_code` で拒否される。\n現時点でサポートしている code の全量はこの endpoint の request example を参照（`GET /v1/tags` でも取得可）。"
          },
          "owner_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "maxItems": 20,
            "description": "オーナー紐付け（parking_lot_owners）。共有物件なら複数指定可。owner セルフ登録（`POST /v1/owner/...`）ではこの field は **受け付けない**。"
          }
        },
        "required": [
          "parking_lot"
        ],
        "additionalProperties": false
      },
      "ParkingLotCorePatch": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "駐車場の表示名（必須）。系列名＋地名の形式が多い。",
            "examples": [
              "晴海西口第一駐車場"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "住所全文。都道府県 + 市区 + 町丁目 + 番地。公開検索の ILIKE 対象。",
            "examples": [
              "東京都中央区晴海20-39"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "active",
              "on_hold",
              "withdrawn",
              "rejected"
            ],
            "description": "駐車場の公開状態 (CHECK 制約付き)。\n- `draft`     : 下書き（Owner が記入中。admin には未通知）\n- `pending`   : 申請中（admin 審査待ち）\n- `active`    : 公開中（search 結果に出る）\n- `on_hold`   : 保留中\n- `withdrawn` : 取下げ\n- `rejected`  : 却下（admin が審査で却下）\nOwner Bundle API では `draft` / `pending` の 2 値のみ受領。`active` 以降の遷移は admin 側 API。",
            "examples": [
              "active"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "manual",
              "s-park",
              "times",
              "repark",
              "navipark",
              "p-colle"
            ],
            "description": "データ起源（codes.category_id=`lot_source`）。\n- `manual`   : 手動登録（admin ポータル / owner セルフ登録のデフォルト）\n- `s-park`   : S-PARK（運営会社 S-PARK のスクレイピング）\n- `times`    : タイムズ（タイムズ 24 のスクレイピング）\n- `repark`   : リパーク（三井のリパーク系列のスクレイピング）\n- `navipark` : ナビパーク\n- `p-colle`  : P-Colle（駐車場情報収集隊の取り込み）",
            "examples": [
              "manual"
            ]
          },
          "lot_type": {
            "type": "string",
            "enum": [
              "regular",
              "meter",
              "ticket",
              "monthly_only",
              "hourly_with_monthly",
              "prepaid_coin"
            ],
            "description": "駐車場種別 (default `regular`)。`meter` / `ticket` を選んだ場合は wizard が物理制限・spots step をスキップする (§17)。",
            "examples": [
              "regular"
            ]
          },
          "vehicle_type_max": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50,
            "description": "lot レベル default の最大車種 (codes.category_id=`vehicle_type`)。spot レベルで上書き可能。NULL = 未設定。",
            "examples": [
              "minivan"
            ]
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "親駐車場 UUID（ブランド系列の名寄せ用）。通常 null。",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "variant_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "同一系列内の区別ラベル（「第1」「第2」「裏口」等）。",
            "examples": [
              "第1"
            ]
          },
          "place_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Google Place ID（archive/032）。Pコレ取り込みの冪等キー兼用で UNIQUE 制約あり。単発登録では null で OK。",
            "examples": [
              "ChIJN1t_tDeuEmsRUsoyG83frY4"
            ]
          },
          "total_spaces": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "駐車可能台数。非負整数。",
            "examples": [
              12
            ]
          },
          "structure": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "flat",
              "self_multi",
              "mechanical"
            ],
            "description": "駐車場構造（codes.category_id=`lot_structure`）。\n- `flat`       : 平置き（屋根なし / 青空）\n- `self_multi` : 自走式立体（自走で各階へ）\n- `mechanical` : 機械式立体（パレット入れ替え）",
            "examples": [
              "mechanical"
            ]
          },
          "entry_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "self",
              "mechanical"
            ],
            "description": "入庫方式（codes.category_id=`lot_entry`）。\n- `self`       : 自走式（自分で停める）\n- `mechanical` : 機械式（パレットに車を載せる）\n`structure=mechanical` なら通常 `entry_method=mechanical`、\n`structure=flat`/`self_multi` なら `entry_method=self` が一般的。",
            "examples": [
              "mechanical"
            ]
          },
          "entry_difficulty": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "easy",
              "normal",
              "hard"
            ],
            "description": "入庫難易度（codes.category_id=`entry_difficulty`）。\n- `easy`   : 簡単\n- `normal` : 普通\n- `hard`   : 難しい",
            "examples": [
              "easy"
            ]
          },
          "max_height_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "車高制限(m)。機械式は 1.55/2.10、自走式は 2.10/2.50 が典型。",
            "examples": [
              1.55
            ]
          },
          "max_width_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "車幅制限(m)。1.85 / 1.95 が典型。",
            "examples": [
              1.95
            ]
          },
          "max_length_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "全長制限(m)。5.00 / 5.30 が典型。",
            "examples": [
              5.3
            ]
          },
          "max_weight_t": {
            "type": [
              "number",
              "null"
            ],
            "description": "重量制限(t)。2.0 / 2.5 が典型。",
            "examples": [
              2.5
            ]
          },
          "min_clearance_cm": {
            "type": [
              "integer",
              "null"
            ],
            "description": "最低地上高(cm)。機械式で特に重要。",
            "examples": [
              10
            ]
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "description": "タイヤ幅制限(mm)。機械式で設定されることが多い。null = 制限なし（検索では合致扱い）。archive/041 で追加。",
            "examples": [
              205
            ]
          },
          "max_parking_duration_min": {
            "type": [
              "integer",
              "null"
            ],
            "description": "最大駐車時間(分)。短時間のみ等の制限がある場合のみ設定。archive/015 で追加。例: `180` = 3 時間。",
            "examples": [
              180
            ]
          },
          "receipt_available": {
            "type": "boolean",
            "description": "領収書発行可否。DEFAULT `false`。",
            "examples": [
              true
            ]
          },
          "operator_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "paraca",
              "times",
              "repark",
              "navipark",
              "mitsui",
              "npc24h",
              "ecoloparc",
              "coinpark",
              "other"
            ],
            "description": "運営会社コード（codes.category_id=`operator`）。\n- `paraca`    : パラカ\n- `times`     : タイムズ24\n- `repark`    : リパーク（三井のリパーク系列）\n- `navipark`  : ナビパーク\n- `mitsui`    : 三井のリパーク（本体）\n- `npc24h`    : NPC24H\n- `ecoloparc` : エコロパーク\n- `coinpark`  : コインパーク\n- `other`     : その他（分類不能 / 個人オーナー等）\n\nowner 自営の駐車場で運営会社が無い場合は `null` で OK。\n新規運営会社は codes マスター追加と schema enum 追記の 2 step で拡張する。",
            "examples": [
              "repark"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "駐車場の説明文（オーナーが自由記述）。UI の詳細ページに表示される。",
            "examples": [
              "駅から徒歩3分の好立地。夜間入庫可能。"
            ]
          },
          "pricing_notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000,
            "description": "料金に関する補足・備考（フリーテキスト）。特典・キャンペーン等の記述に使用。",
            "examples": [
              "月極契約は別途お問い合わせください。"
            ]
          },
          "contact_phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "連絡先電話番号。最大 20 文字。",
            "examples": [
              "03-1234-5678"
            ]
          },
          "contact_email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "format": "email",
            "description": "連絡先メールアドレス。RFC 5321 準拠。最大 200 文字。",
            "examples": [
              "parking@example.com"
            ]
          },
          "contact_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "format": "uri",
            "description": "連絡先 URL（問い合わせフォーム等）。最大 500 文字。",
            "examples": [
              "https://example.com/contact"
            ]
          },
          "capacity_kei": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "軽自動車専用枠数。0〜9999 の整数。",
            "examples": [
              5
            ]
          },
          "capacity_regular": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "普通車枠数。0〜9999 の整数。",
            "examples": [
              20
            ]
          },
          "capacity_oversized": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "大型車枠数。0〜9999 の整数。",
            "examples": [
              2
            ]
          },
          "capacity_motorcycle": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "バイク専用枠数。0〜9999 の整数。",
            "examples": [
              3
            ]
          },
          "capacity_ev": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "EV 充電器付き枠数。0〜9999 の整数。",
            "examples": [
              4
            ]
          },
          "capacity_disabled": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9999,
            "description": "車椅子（障がい者）専用枠数。0〜9999 の整数。",
            "examples": [
              2
            ]
          },
          "raw_text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000,
            "description": "取り込み元の生テキスト（CSV / スクレイピングの原文）。Pコレ import が JSON 化して格納。UI からは通常送らない（null で OK）。",
            "examples": [
              null
            ]
          },
          "entry_exit_locations": {
            "$ref": "#/components/schemas/EntryExitLocations"
          },
          "geometry": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ParkingLotGeometry"
              },
              {
                "description": "駐車場の座標 / 形状（shape_type と座標をペアで持つ nested object）。Create では省略時 `null`（座標未設定）。Patch では省略=触らない、`null`=座標クリア、object 指定で置換。"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "ParkingLotTagPatchInput": {
        "type": "object",
        "properties": {
          "tag_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[a-z][a-z0-9_]*$",
            "description": "タグを一意に識別するコード値。`tags.slug` を参照する snake_case 文字列。\n現時点でサポートしている値は OAS 例 / `GET /v1/tags` のレスポンスを参照。\n未知の code を送ると 400 `unknown_tag_code` で拒否される。",
            "examples": [
              "ev_charging"
            ]
          },
          "state": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "タグ状態。`true` / `false` / `null` の 3 値。`null` を送るとそのタグを `parking_lot_tags` から削除（= 不明に戻す）。",
            "examples": [
              true
            ]
          }
        },
        "required": [
          "tag_code",
          "state"
        ],
        "additionalProperties": false
      },
      "ParkingLotBundleUpdateBody": {
        "type": "object",
        "properties": {
          "parking_lot": {
            "$ref": "#/components/schemas/ParkingLotCorePatch"
          },
          "hours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotHourInput"
            },
            "maxItems": 50
          },
          "pricing_rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotPricingRuleInput"
            },
            "maxItems": 100
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotImageInput"
            },
            "maxItems": 50
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotTagPatchInput"
            },
            "maxItems": 100,
            "description": "タグ紐付け（parking_lot_tags）。`tag_code` は `tags.slug` を指すコード値。\n`state=null` を送るとそのタグを `parking_lot_tags` から削除（= 不明に戻す）。\nbody に含まれていない tag は変更されない（= 完全置換ではなく差分適用）。"
          },
          "owner_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "maxItems": 20
          }
        },
        "additionalProperties": false
      },
      "AdminParkingLotPricingGroup": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "display_order": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "code",
          "name",
          "is_default",
          "display_order",
          "created_at",
          "updated_at"
        ]
      },
      "AdminPricingGroupCreateBody": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "is_default": {
            "type": "boolean"
          },
          "display_order": {
            "type": "integer"
          }
        },
        "required": [
          "code",
          "name"
        ]
      },
      "AdminPricingGroupUpdateBody": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "is_default": {
            "type": "boolean"
          },
          "display_order": {
            "type": "integer"
          }
        }
      },
      "DateOverrideHoursBlock": {
        "type": "object",
        "properties": {
          "is_closed": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "is_24h": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "open_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$"
          },
          "close_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$"
          },
          "allowed": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "DateOverrideHoursMap": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "business": {
            "$ref": "#/components/schemas/DateOverrideHoursBlock"
          },
          "entry": {
            "$ref": "#/components/schemas/DateOverrideHoursBlock"
          },
          "exit": {
            "$ref": "#/components/schemas/DateOverrideHoursBlock"
          },
          "after_hours_exit": {
            "$ref": "#/components/schemas/DateOverrideHoursBlock"
          }
        },
        "additionalProperties": false,
        "description": "営業時間オーバーライド。window_type 毎に block を指定。省略キーは通常ルールへフォールバック。"
      },
      "ParkingLotDateOverride": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "override_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "対象日 (JST)",
            "examples": [
              "2026-07-25"
            ]
          },
          "label": {
            "type": "string",
            "description": "例外の名称",
            "examples": [
              "隅田川花火大会"
            ]
          },
          "hours": {
            "$ref": "#/components/schemas/DateOverrideHoursMap"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "社内メモ（任意）"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "examples": [
              "2026-04-17T12:00:00Z"
            ]
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "override_date",
          "label",
          "hours",
          "note",
          "updated_at"
        ]
      },
      "DateOverrideUpsertBody": {
        "type": "object",
        "properties": {
          "override_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "hours": {
            "$ref": "#/components/schemas/DateOverrideHoursMap"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        },
        "required": [
          "override_date",
          "label"
        ],
        "additionalProperties": false
      },
      "DateOverridePatchBody": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "hours": {
            "$ref": "#/components/schemas/DateOverrideHoursMap"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        },
        "additionalProperties": false
      },
      "ParkingLotHour": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "window_type": {
            "type": "string",
            "enum": [
              "business",
              "entry",
              "exit",
              "after_hours_exit"
            ],
            "description": "時間窓の種別（codes.category_id=`parking_hours_window_type`）。\n- `business`         : 通常営業時間（既存行はすべてこれ扱い。DEFAULT）\n- `entry`            : 入庫可能時間（夜間入庫不可施設など）\n- `exit`             : 出庫可能時間\n- `after_hours_exit` : 営業時間外の出庫可否フラグ（曜日単位）",
            "examples": [
              "business"
            ]
          },
          "day_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "all",
              "weekday",
              "saturday",
              "sunday",
              "holiday",
              "holiday_eve"
            ]
          },
          "day_of_week": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 6
          },
          "is_24h": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "is_closed": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "open_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$"
          },
          "close_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$"
          },
          "effective_from": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "effective_to": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "examples": [
              "2026-04-17T12:00:00Z"
            ]
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "window_type",
          "day_type",
          "day_of_week",
          "is_24h",
          "is_closed",
          "open_time",
          "close_time",
          "effective_from",
          "effective_to",
          "updated_at"
        ]
      },
      "ParkingLotHoursReplaceBody": {
        "type": "object",
        "properties": {
          "window_type": {
            "type": "string",
            "enum": [
              "business",
              "entry",
              "exit",
              "after_hours_exit"
            ],
            "description": "置換対象の window_type。省略時は全 window_type を一括置換する（= `parking_lot_hours` の該当 lot 行を完全リセット）。",
            "examples": [
              "business"
            ]
          },
          "hours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParkingLotHourInput"
            },
            "maxItems": 50,
            "description": "置換後の hours 配列。window_type 指定時は全要素がその window_type である必要がある（body の `window_type` と異なる要素は 400）。"
          }
        },
        "required": [
          "hours"
        ],
        "additionalProperties": false
      },
      "AdminParkingLotIngestPayloadItem": {
        "type": "object",
        "properties": {
          "payload_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string"
          },
          "external_id": {
            "type": "string"
          },
          "captured_at": {
            "type": "string"
          },
          "ingest_run_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "run_started_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "run_triggered_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_method": {
            "type": "string",
            "enum": [
              "auto",
              "manual",
              "unknown"
            ]
          },
          "reason_summary": {
            "type": "string"
          }
        },
        "required": [
          "payload_id",
          "source",
          "external_id",
          "captured_at",
          "ingest_run_id",
          "run_started_at",
          "run_triggered_by",
          "link_method",
          "reason_summary"
        ]
      },
      "AdminParkingLotIngestPayloadsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingLotIngestPayloadItem"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "GeoJSONGeometry": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "Point",
              "LineString",
              "Polygon",
              "MultiPoint",
              "MultiLineString",
              "MultiPolygon",
              "GeometryCollection"
            ]
          },
          "coordinates": {},
          "geometries": {
            "type": "array",
            "items": {}
          }
        },
        "required": [
          "type"
        ],
        "additionalProperties": {}
      },
      "PricingRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "category": {
            "type": "string"
          },
          "rule_order": {
            "type": "integer"
          },
          "time_start": {
            "type": [
              "string",
              "null"
            ]
          },
          "time_end": {
            "type": [
              "string",
              "null"
            ]
          },
          "day_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "per_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "cap_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "cap_duration_hours": {
            "type": [
              "number",
              "null"
            ]
          },
          "cap_price_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "cap_repeat": {
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "category",
          "rule_order",
          "time_start",
          "time_end",
          "day_type",
          "per_minutes",
          "price_minor",
          "cap_type",
          "cap_duration_hours",
          "cap_price_minor",
          "cap_repeat"
        ]
      },
      "ParkingLotDetailImage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "asset_id": {
            "type": "string",
            "format": "uuid"
          },
          "is_main": {
            "type": "boolean"
          },
          "sort_order": {
            "type": [
              "integer",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "公開 URL (R2_PUBLIC_BASE_URL + s3_key)。asset 削除済 / 未配置の場合は null。"
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "asset_id",
          "is_main",
          "sort_order",
          "image_url"
        ]
      },
      "ParkingLotTag": {
        "type": "object",
        "properties": {
          "tag_id": {
            "type": "string",
            "format": "uuid"
          },
          "state": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "tag": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "color": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "slug": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "icon_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "color",
              "slug",
              "category"
            ]
          }
        },
        "required": [
          "tag_id",
          "tag"
        ]
      },
      "ParkingLotDetailOperator": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "color"
        ]
      },
      "ParkingLotFieldValueIds": {
        "type": "object",
        "additionalProperties": {
          "type": "string",
          "format": "uuid"
        },
        "description": "include=field_value_ids 指定時に返る per-field の primary parking_field_values.id。web/home の ✓/✗ tap ボタンが POST 先 endpoint の path param として利用する。"
      },
      "ParkingLotPublicSpot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "vehicle_type_max": {
            "type": [
              "string",
              "null"
            ],
            "description": "対応車種上限 (codes(category='vehicle_type') の code)"
          },
          "is_ev_charger": {
            "type": "boolean"
          },
          "ev_connector_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "EV コネクタ種別 (codes(category='ev_connector_type'))"
          },
          "accessibility": {
            "type": [
              "string",
              "null"
            ],
            "description": "アクセシビリティ属性 (codes(category='accessibility')、例: wheelchair / parent_child)"
          },
          "is_reservable": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "code",
          "vehicle_type_max",
          "is_ev_charger",
          "ev_connector_type",
          "accessibility",
          "is_reservable"
        ]
      },
      "ParkingLotPublicPricingGroup": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "display_order": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "is_default",
          "display_order"
        ]
      },
      "ParkingLot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "total_spaces": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "structure": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "max_height_m": {
            "type": [
              "number",
              "null"
            ]
          },
          "max_width_m": {
            "type": [
              "number",
              "null"
            ]
          },
          "max_length_m": {
            "type": [
              "number",
              "null"
            ]
          },
          "max_weight_t": {
            "type": [
              "number",
              "null"
            ]
          },
          "min_clearance_cm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_parking_duration_min": {
            "type": [
              "integer",
              "null"
            ]
          },
          "entry_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ]
          },
          "shape_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "area": {
            "$ref": "#/components/schemas/GeoJSONGeometry"
          },
          "operator_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "entry_difficulty": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_type": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "address",
          "lat",
          "lng",
          "total_spaces",
          "status",
          "structure",
          "created_at"
        ]
      },
      "ParkingLotWithDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ParkingLot"
          },
          {
            "type": "object",
            "properties": {
              "pricing_rules": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PricingRule"
                }
              },
              "images": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParkingLotDetailImage"
                }
              },
              "tags": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParkingLotTag"
                }
              },
              "operator": {
                "$ref": "#/components/schemas/ParkingLotDetailOperator"
              },
              "field_value_ids": {
                "$ref": "#/components/schemas/ParkingLotFieldValueIds"
              },
              "is_open_now": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "現在営業中かどうか（null = 営業時間未設定 or RPC 失敗）。後方互換フィールド。"
              },
              "derived": {
                "type": "object",
                "properties": {
                  "is_open_now": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "can_enter_now": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "can_exit_now": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                },
                "required": [
                  "is_open_now",
                  "can_enter_now",
                  "can_exit_now"
                ],
                "description": "RPC から派生した営業・入出庫可否フラグ。2026-04-24 追加。従来の flat `is_open_now` も並存して返す（後方互換）。"
              },
              "hours": {
                "type": "object",
                "properties": {
                  "windows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "window_type": {
                          "type": "string",
                          "enum": [
                            "business",
                            "entry",
                            "exit",
                            "after_hours_exit"
                          ]
                        },
                        "day_type": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "weekday",
                            "saturday",
                            "sunday",
                            "holiday",
                            "holiday_eve",
                            "all"
                          ]
                        },
                        "day_of_week": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "is_24h": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "is_closed": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "open_time": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "close_time": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "effective_from": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "effective_to": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "window_type",
                        "day_type",
                        "day_of_week",
                        "is_24h",
                        "is_closed",
                        "open_time",
                        "close_time",
                        "effective_from",
                        "effective_to"
                      ]
                    }
                  },
                  "legacy_business": {
                    "type": "array",
                    "items": {}
                  }
                },
                "required": [
                  "windows"
                ],
                "description": "営業時間 windows 配列（window_type × day_type × 期間）。2026-04-24 追加。`include=hours` で有効。"
              },
              "date_overrides": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "override_date": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "hours": {},
                    "pricing": {},
                    "note": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "override_date",
                    "label",
                    "note"
                  ]
                },
                "description": "当日〜30 日先の日付オーバーライド配列。2026-04-24 追加。`include=date_overrides` で有効。"
              },
              "regulations": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "regulation_type": {
                      "type": "string"
                    },
                    "fee_yen": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "time_limit_min": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "badge_only": {
                      "type": "boolean"
                    },
                    "motorcycle_ok": {
                      "type": "boolean"
                    },
                    "normal_car_ok": {
                      "type": "boolean"
                    },
                    "cargo_ok": {
                      "type": "boolean"
                    },
                    "has_restriction": {
                      "type": "boolean"
                    },
                    "overlap_regulations": {},
                    "external_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "source": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "regulation_type",
                    "fee_yen",
                    "time_limit_min",
                    "badge_only",
                    "motorcycle_ok",
                    "normal_car_ok",
                    "cargo_ok",
                    "has_restriction",
                    "external_id",
                    "source"
                  ]
                },
                "description": "tokyometer 等の駐車規制データ (パーキング・チケット / メーター)。Phase 0.5+ で追加 (master.rules から parking_lot_regulations に分離)。`include=regulations` で有効。"
              },
              "highlighted_fields": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "検索条件（highlight_* クエリパラメータ）にマッチしたセクション名リスト。 検索結果 → 詳細画面遷移時に UI のハイライト対象を判定するために返す。 値の例: `[\"pricing\", \"roof\", \"operating_hours\", \"features\", \"vehicle_type\"]`。 highlight_* QP が 1 つも指定されない場合は field 自体が省略される。"
              },
              "highlighted_tag_slugs": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "検索条件にマッチしたタグ slug の具体リスト（features / roof / open_24h の UI チップ個別ハイライト用）。 `highlighted_fields` がセクション単位なのに対し、この field はタグ単位で「どのチップを光らせるか」を示す。 roof_only / open_24h の alias 展開後の実在 slug（例: `covered` / `roof` / `indoor` / `open_24h`）を含む。"
              },
              "spots": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParkingLotPublicSpot"
                },
                "description": "駐車場内の個別車室一覧（公開可能属性のみ）。soft delete (deleted_at) と is_active=false は除外。 EV 充電器・アクセシビリティ・対応車種など UI の「設備」表示や検索適合チェックに使う。 1 駐車場で複数 spot を持たないレガシー駐車場では空配列。"
              },
              "pricing_groups": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParkingLotPublicPricingGroup"
                },
                "description": "駐車場内の料金プラン (pricing group) 一覧。display_order ASC, code ASC 順。 is_default=true の group が UI の主表示用。複数あれば「他の料金プラン」セクションを出す。 個別 group の rule は既存の `include=pricing_rules` から pricing_group_id で照合。"
              }
            }
          }
        ]
      },
      "AdminParkingLotStatusChangeResult": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "active",
              "on_hold",
              "withdrawn",
              "rejected"
            ]
          },
          "to": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "active",
              "on_hold",
              "withdrawn",
              "rejected"
            ]
          },
          "fields_approved": {
            "type": "integer"
          }
        },
        "required": [
          "from",
          "to",
          "fields_approved"
        ]
      },
      "AdminParkingLotStatusChangeBody": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "active",
              "on_hold",
              "withdrawn",
              "rejected"
            ]
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "status"
        ]
      },
      "AdminParkingSpot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "pricing_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "exclusiveMinimum": 0
          },
          "vehicle_type_max": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_ev_charger": {
            "type": "boolean"
          },
          "ev_connector_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "ev_charge_power_kw": {
            "type": [
              "number",
              "null"
            ]
          },
          "ev_charge_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "ev_charge_operator": {
            "type": [
              "string",
              "null"
            ]
          },
          "ev_charge_billing": {
            "type": [
              "string",
              "null"
            ]
          },
          "ev_charge_network_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "accessibility": {
            "type": [
              "string",
              "null"
            ]
          },
          "width_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "length_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_weight_kg": {
            "type": [
              "integer",
              "null"
            ]
          },
          "min_clearance_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "is_reservable": {
            "type": "boolean"
          },
          "is_active": {
            "type": "boolean"
          },
          "meta": {
            "type": "object",
            "additionalProperties": {}
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "pricing_group_id",
          "code",
          "count",
          "vehicle_type_max",
          "is_ev_charger",
          "ev_connector_type",
          "ev_charge_power_kw",
          "ev_charge_type",
          "ev_charge_operator",
          "ev_charge_billing",
          "ev_charge_network_id",
          "accessibility",
          "width_mm",
          "length_mm",
          "height_mm",
          "max_weight_kg",
          "min_clearance_mm",
          "max_tire_width_mm",
          "is_reservable",
          "is_active",
          "meta",
          "created_at",
          "updated_at",
          "deleted_at"
        ]
      },
      "AdminParkingSpotCreateBody": {
        "type": "object",
        "properties": {
          "parking_lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "pricing_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$"
          },
          "count": {
            "type": "integer",
            "exclusiveMinimum": 0
          },
          "vehicle_type_max": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_ev_charger": {
            "type": "boolean"
          },
          "ev_connector_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "ev_charge_power_kw": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0
          },
          "ev_charge_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ac",
              "dc",
              "ac_three_phase"
            ]
          },
          "ev_charge_operator": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 64
          },
          "ev_charge_billing": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "separate",
              "included",
              "free"
            ]
          },
          "ev_charge_network_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 128
          },
          "accessibility": {
            "type": [
              "string",
              "null"
            ]
          },
          "width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "length_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "height_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "max_weight_kg": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "min_clearance_mm": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "is_reservable": {
            "type": "boolean"
          },
          "is_active": {
            "type": "boolean"
          },
          "meta": {
            "type": "object",
            "additionalProperties": {}
          }
        },
        "required": [
          "parking_lot_id",
          "code"
        ]
      },
      "AdminParkingSpotUpdateBody": {
        "type": "object",
        "properties": {
          "pricing_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$"
          },
          "count": {
            "type": "integer",
            "exclusiveMinimum": 0
          },
          "vehicle_type_max": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_ev_charger": {
            "type": "boolean"
          },
          "ev_connector_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "ev_charge_power_kw": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0
          },
          "ev_charge_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ac",
              "dc",
              "ac_three_phase"
            ]
          },
          "ev_charge_operator": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 64
          },
          "ev_charge_billing": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "separate",
              "included",
              "free"
            ]
          },
          "ev_charge_network_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 128
          },
          "accessibility": {
            "type": [
              "string",
              "null"
            ]
          },
          "width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "length_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "height_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "max_weight_kg": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "min_clearance_mm": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "max_tire_width_mm": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "is_reservable": {
            "type": "boolean"
          },
          "is_active": {
            "type": "boolean"
          },
          "meta": {
            "type": "object",
            "additionalProperties": {}
          }
        }
      },
      "AdminAppUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "auth_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "display_name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "vehicle_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "premium": {
            "type": "boolean"
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "auth_user_id",
          "display_name",
          "email",
          "vehicle_type",
          "premium",
          "status",
          "created_at"
        ]
      },
      "UserActivityCountRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "activity_type": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "last_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "user_id",
          "activity_type",
          "count",
          "last_at"
        ]
      },
      "AdminTag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "sort_order": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "icon_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "color",
          "sort_order",
          "created_at",
          "slug",
          "category",
          "icon_name"
        ]
      },
      "AdminOperator": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "sort_order": {
            "type": "integer"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "color",
          "sort_order",
          "created_at",
          "updated_at",
          "deleted_at"
        ]
      },
      "ParkingOperator": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "name_en": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "parent_company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_brand": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "name_en",
          "logo_asset_id",
          "website",
          "parent_company_name",
          "is_brand",
          "sort_order",
          "is_active",
          "created_at",
          "updated_at",
          "deleted_at"
        ]
      },
      "ParkingOperatorCreateBody": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[a-z0-9_]+$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "name_en": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "logo_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "parent_company_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "is_brand": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          }
        },
        "required": [
          "code",
          "name"
        ]
      },
      "ParkingOperatorUpdateBody": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[a-z0-9_]+$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "name_en": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "logo_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "parent_company_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "is_brand": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          }
        }
      },
      "AdminNotification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "avatar": {
            "type": "string"
          },
          "read": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "message",
          "category",
          "avatar",
          "read",
          "created_at",
          "deleted_at"
        ]
      },
      "AdminUserNotification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_recipients": {
            "type": [
              "integer",
              "null"
            ]
          },
          "success_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "failure_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "body",
          "type",
          "target",
          "status",
          "scheduled_at",
          "sent_at",
          "total_recipients",
          "success_count",
          "failure_count",
          "created_at",
          "deleted_at"
        ]
      },
      "DispatchEnqueued": {
        "type": "object",
        "properties": {
          "notification_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "enqueued": {
            "type": "integer"
          },
          "batches": {
            "type": "integer"
          }
        },
        "required": [
          "notification_id",
          "enqueued",
          "batches"
        ]
      },
      "UserNotificationUpdate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "body": {
            "type": "string",
            "maxLength": 2000
          },
          "type": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "total_recipients": {
            "type": [
              "integer",
              "null"
            ]
          },
          "success_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "failure_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ReviewStatus": {
        "type": "string",
        "enum": [
          "pending",
          "approved",
          "rejected",
          "hidden"
        ]
      },
      "ReviewRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ReviewStatus"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "user_id",
          "rating",
          "comment",
          "status",
          "created_at"
        ]
      },
      "AdminReview": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ReviewRow"
          },
          {
            "type": "object",
            "properties": {
              "user_id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "rating": {
                "type": "integer"
              },
              "admin_note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "admin_note",
              "deleted_at"
            ]
          }
        ]
      },
      "SupportTicketPriority": {
        "type": "string",
        "enum": [
          "urgent",
          "high",
          "medium",
          "low"
        ]
      },
      "SupportTicketStatus": {
        "type": "string",
        "enum": [
          "new",
          "in_progress",
          "completed",
          "on_hold"
        ]
      },
      "AdminSupportTicket": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_email": {
            "type": "string"
          },
          "user_name": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "priority": {
            "$ref": "#/components/schemas/SupportTicketPriority"
          },
          "status": {
            "$ref": "#/components/schemas/SupportTicketStatus"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "user_email",
          "user_name",
          "subject",
          "body",
          "category",
          "priority",
          "status",
          "created_at"
        ]
      },
      "SupportTicketUpdate": {
        "type": "object",
        "properties": {
          "subject": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/SupportTicketStatus"
          },
          "priority": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SupportTicketPriority"
              },
              {
                "type": [
                  "string",
                  "null"
                ]
              }
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigned_admin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "internal_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminErrorReport": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_email": {
            "type": "string"
          },
          "user_name": {
            "type": "string"
          },
          "parking_lot_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "parking_lot_name": {
            "type": "string"
          },
          "report_type": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "severity": {
            "type": [
              "string",
              "null"
            ]
          },
          "evidence_urls": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "resolved_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "admin_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "user": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "display_name"
            ]
          },
          "parking_lot": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          }
        },
        "required": [
          "id",
          "user_id",
          "user_email",
          "user_name",
          "parking_lot_id",
          "parking_lot_name",
          "report_type",
          "description",
          "status",
          "severity",
          "evidence_urls",
          "resolved_by",
          "admin_note",
          "created_at",
          "resolved_at"
        ]
      },
      "ErrorReportUpdate": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "user_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "parking_lot_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "parking_lot_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "report_type": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "severity": {
            "type": [
              "string",
              "null"
            ]
          },
          "evidence_urls": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "resolved_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "admin_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdminTaskKind": {
        "type": "string",
        "enum": [
          "misinformation_report",
          "support",
          "owner_application",
          "parking_new_registration"
        ]
      },
      "AdminTaskUrgency": {
        "type": "string",
        "enum": [
          "urgent",
          "high",
          "medium",
          "low"
        ]
      },
      "AdminTaskResource": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "subtitle": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "title",
          "subtitle",
          "status"
        ]
      },
      "AdminTask": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "task_kind": {
            "$ref": "#/components/schemas/AdminTaskKind"
          },
          "ref_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "assignee_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "urgency": {
            "$ref": "#/components/schemas/AdminTaskUrgency"
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "resource": {
            "$ref": "#/components/schemas/AdminTaskResource"
          }
        },
        "required": [
          "id",
          "task_kind",
          "ref_id",
          "assignee_id",
          "urgency",
          "due_at",
          "memo",
          "created_at",
          "updated_at"
        ]
      },
      "AdminTaskUpdate": {
        "type": "object",
        "properties": {
          "assignee_admin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "urgency": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdminTaskUrgency"
              },
              {
                "type": [
                  "string",
                  "null"
                ]
              }
            ]
          },
          "status": {
            "type": "string"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdminArticle": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string"
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "view_count": {
            "type": "integer"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "publish_to_web": {
            "type": "boolean"
          },
          "publish_to_app": {
            "type": "boolean"
          },
          "hero_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "hero_image_alt_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "author_slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_format": {
            "type": "string"
          },
          "related_hubs": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "body",
          "excerpt",
          "category",
          "author_name",
          "status",
          "published_at",
          "view_count",
          "tags",
          "thumbnail_url",
          "created_at",
          "updated_at"
        ]
      },
      "ArticleUpdate": {
        "type": "object",
        "properties": {
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string"
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "author_slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "hero_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "hero_image_alt_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_format": {
            "type": "string"
          },
          "publish_to_web": {
            "type": "boolean"
          },
          "publish_to_app": {
            "type": "boolean"
          },
          "related_hubs": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "AdminArticleTag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "sort_order": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "sort_order",
          "created_at"
        ]
      },
      "AdminAd": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "ad_type": {
            "type": "string"
          },
          "placement": {
            "type": "string"
          },
          "banner_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "alt_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "end_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "impressions": {
            "type": "integer"
          },
          "clicks": {
            "type": "integer"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "ad_type",
          "placement",
          "banner_url",
          "link_url",
          "alt_text",
          "status",
          "start_date",
          "end_date",
          "impressions",
          "clicks",
          "memo",
          "created_at",
          "updated_at"
        ]
      },
      "AdUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "ad_type": {
            "type": "string"
          },
          "placement": {
            "type": "string"
          },
          "banner_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "alt_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "end_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdminBoost": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "credit_per_impression": {
            "type": "integer"
          },
          "daily_budget": {
            "type": [
              "integer",
              "null"
            ]
          },
          "total_budget": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "email"
            ]
          },
          "parking_lot": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "address"
            ]
          }
        },
        "required": [
          "id",
          "owner_id",
          "parking_lot_id",
          "name",
          "credit_per_impression",
          "daily_budget",
          "total_budget",
          "status",
          "started_at",
          "ended_at"
        ]
      },
      "BoostUpdate": {
        "type": "object",
        "properties": {
          "owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "credit_per_impression": {
            "type": "integer"
          },
          "daily_budget": {
            "type": [
              "integer",
              "null"
            ]
          },
          "total_budget": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminSponsor": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "banner_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "category",
          "description",
          "logo_url",
          "banner_url",
          "link_url"
        ]
      },
      "SponsorUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "banner_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AdminOwner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "owner_type": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "representative_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "parking_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "owner_type",
          "name",
          "email",
          "phone",
          "status",
          "company_name",
          "representative_name",
          "created_at",
          "updated_at"
        ]
      },
      "OwnerUpdate": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner_type": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdminOwnerPasswordResetResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "email_status": {
            "type": "string",
            "enum": [
              "sent",
              "failed"
            ]
          },
          "email_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "ok",
          "owner_id",
          "email",
          "email_status",
          "email_error"
        ]
      },
      "AdminOwnerPasswordTokenHistoryItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "purpose": {
            "type": "string",
            "enum": [
              "invite",
              "reset"
            ]
          },
          "sent_to_email": {
            "type": "string"
          },
          "issued_by_admin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "issued_at": {
            "type": "string"
          },
          "expires_at": {
            "type": "string"
          },
          "used_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "invalidated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "consumed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "used",
              "consumed",
              "expired",
              "invalidated"
            ]
          }
        },
        "required": [
          "id",
          "purpose",
          "sent_to_email",
          "issued_by_admin_id",
          "issued_at",
          "expires_at",
          "used_at",
          "invalidated_at",
          "consumed_at",
          "state"
        ]
      },
      "AdminOwnerCredit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "balance": {
            "type": "integer"
          },
          "total_purchased": {
            "type": "integer"
          },
          "total_consumed": {
            "type": "integer"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "owner_type": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "email",
              "owner_type"
            ]
          }
        },
        "required": [
          "id",
          "owner_id",
          "balance",
          "total_purchased",
          "total_consumed",
          "owner"
        ]
      },
      "AdminCreditTransaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "amount": {
            "type": "integer"
          },
          "balance_after": {
            "type": "integer"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "boost_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "owner_id",
          "type",
          "amount",
          "balance_after",
          "description",
          "boost_id",
          "created_at"
        ]
      },
      "AdminOwnerApplication": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "owner_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "owner_name": {
            "type": "string"
          },
          "owner_email": {
            "type": "string"
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "proof_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "additional_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "owner": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "owner_type": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "email",
              "owner_type"
            ]
          },
          "parking_lot": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "address"
            ]
          },
          "proof_asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          },
          "reviewer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          }
        },
        "required": [
          "id",
          "owner_id",
          "owner_name",
          "owner_email",
          "company_name",
          "parking_lot_id",
          "status",
          "reviewed_by",
          "reviewed_at",
          "reject_reason",
          "created_at"
        ]
      },
      "AdminOwnerApplicationUpdate": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "additional_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "AdminOwnerInquiry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "owner_type": {
            "type": "string"
          },
          "inquiry_kind": {
            "type": "string"
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_name_kana": {
            "type": [
              "string",
              "null"
            ]
          },
          "representative_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_name": {
            "type": "string"
          },
          "contact_name_kana": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_email": {
            "type": "string"
          },
          "contact_phone": {
            "type": "string"
          },
          "contact_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_name": {
            "type": "string"
          },
          "lot_address": {
            "type": "string"
          },
          "lot_capacity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "ownership_type": {
            "type": "string"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "consent_documents": {
            "type": "boolean"
          },
          "consent_privacy": {
            "type": "boolean"
          },
          "status": {
            "type": "string"
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "approved_owner_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "owner_type",
          "inquiry_kind",
          "company_name",
          "company_name_kana",
          "representative_name",
          "contact_name",
          "contact_name_kana",
          "contact_email",
          "contact_phone",
          "contact_address",
          "lot_name",
          "lot_address",
          "lot_capacity",
          "ownership_type",
          "notes",
          "consent_documents",
          "consent_privacy",
          "status",
          "reviewed_by",
          "reviewed_at",
          "reject_reason",
          "approved_owner_id",
          "created_at",
          "updated_at"
        ]
      },
      "AdminOwnerInquiryAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "asset_id": {
            "type": "string",
            "format": "uuid"
          },
          "doc_kind": {
            "type": "string"
          },
          "uploaded_via_token": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "file_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "アップロード時の元ファイル名。"
          },
          "mime_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "file_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "download_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "管理者が R2 オブジェクトをダウンロード / プレビューするための presigned GET URL。10 分有効。"
          }
        },
        "required": [
          "id",
          "asset_id",
          "doc_kind",
          "uploaded_via_token",
          "created_at",
          "file_name",
          "mime_type",
          "file_size",
          "download_url"
        ]
      },
      "AdminOwnerInquiryDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdminOwnerInquiry"
          },
          {
            "type": "object",
            "properties": {
              "assets": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AdminOwnerInquiryAsset"
                }
              }
            },
            "required": [
              "assets"
            ]
          }
        ]
      },
      "AdminOwnerInquiryUpdate": {
        "type": "object",
        "properties": {
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "in_progress",
              "documents_requested",
              "documents_submitted",
              "documents_under_review",
              "approved",
              "rejected"
            ]
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        }
      },
      "IssueUploadUrlResponse": {
        "type": "object",
        "properties": {
          "inquiry": {
            "$ref": "#/components/schemas/AdminOwnerInquiry"
          },
          "upload_url": {
            "type": "string",
            "format": "uri"
          },
          "token_id": {
            "type": "string",
            "format": "uuid"
          },
          "expires_at": {
            "type": "string"
          },
          "email_status": {
            "type": "string",
            "enum": [
              "sent",
              "failed"
            ]
          },
          "email_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "inquiry",
          "upload_url",
          "token_id",
          "expires_at",
          "email_status",
          "email_error"
        ]
      },
      "ApproveOwnerInquiryResponse": {
        "type": "object",
        "properties": {
          "inquiry": {
            "$ref": "#/components/schemas/AdminOwnerInquiry"
          },
          "owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "auth_user_id": {
            "type": "string",
            "format": "uuid"
          },
          "email_status": {
            "type": "string",
            "enum": [
              "sent",
              "failed"
            ]
          },
          "email_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "inquiry",
          "owner_id",
          "auth_user_id",
          "email_status",
          "email_error"
        ]
      },
      "RejectOwnerInquiryBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        },
        "required": [
          "reason"
        ]
      },
      "AdminActivityLog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "admin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "admin_email_snapshot": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "target_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "ip_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_agent": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "admin_id",
          "admin_email_snapshot",
          "action",
          "target_type",
          "target_id",
          "target_label",
          "metadata",
          "ip_address",
          "user_agent",
          "created_at"
        ]
      },
      "RevenueMonthlyRow": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "channel": {
            "type": "string"
          },
          "amount_minor": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          }
        },
        "required": [
          "month",
          "channel",
          "amount_minor",
          "count"
        ]
      },
      "RevenueTransaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "occurred_at": {
            "type": "string"
          },
          "channel": {
            "type": "string"
          },
          "transaction_type": {
            "type": "string"
          },
          "amount_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "plan_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "occurred_at",
          "channel",
          "transaction_type",
          "amount_minor",
          "currency",
          "external_id",
          "description",
          "user_id",
          "plan_id",
          "created_at"
        ]
      },
      "RevenueTransactionWithRelations": {
        "allOf": [
          {
            "$ref": "#/components/schemas/RevenueTransaction"
          },
          {
            "type": "object",
            "properties": {
              "user": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "display_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "display_name",
                  "email"
                ]
              },
              "plan": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "code",
                  "name"
                ]
              }
            },
            "required": [
              "user",
              "plan"
            ]
          }
        ]
      },
      "AdminUserSubscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "plan_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "status": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "current_period_end": {
            "type": [
              "string",
              "null"
            ]
          },
          "trial_ends_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancel_at_period_end": {
            "type": "boolean"
          },
          "external_subscription_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "pricing_variant": {
            "type": [
              "string",
              "null"
            ]
          },
          "campaign_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "user_id",
          "plan_id",
          "status",
          "started_at",
          "ended_at",
          "current_period_end",
          "trial_ends_at",
          "cancel_at_period_end",
          "external_subscription_id",
          "payment_provider",
          "pricing_variant",
          "campaign_id",
          "platform"
        ]
      },
      "AdminSubscriptionPlan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "billing_period": {
            "type": "string"
          },
          "accent_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "plan_group": {
            "type": [
              "string",
              "null"
            ]
          },
          "variant_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "trial_days": {
            "type": "integer"
          },
          "discount_percent": {
            "type": "integer"
          },
          "is_test_variant": {
            "type": "boolean"
          },
          "platform_product_ids": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "description",
          "price_minor",
          "currency",
          "billing_period",
          "accent_color",
          "features",
          "sort_order",
          "is_active",
          "plan_group",
          "variant_label",
          "trial_days",
          "discount_percent",
          "is_test_variant",
          "platform_product_ids",
          "created_at",
          "updated_at"
        ]
      },
      "AdminSubscriptionPlanCreate": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "price_minor": {
            "type": "integer",
            "minimum": 0
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "default": "JPY"
          },
          "billing_period": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20
          },
          "accent_color": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "default": []
          },
          "sort_order": {
            "type": "integer",
            "default": 0
          },
          "is_active": {
            "type": "boolean",
            "default": true
          },
          "plan_group": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80
          },
          "variant_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "trial_days": {
            "type": "integer",
            "minimum": 0,
            "default": 0
          },
          "discount_percent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 99,
            "default": 0
          },
          "is_test_variant": {
            "type": "boolean",
            "default": false
          },
          "platform_product_ids": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "default": {}
          }
        },
        "required": [
          "code",
          "name",
          "price_minor",
          "billing_period"
        ]
      },
      "AdminPricingCampaign": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "applies_to_plan_group": {
            "type": [
              "string",
              "null"
            ]
          },
          "applies_to_billing_period": {
            "type": [
              "string",
              "null"
            ]
          },
          "applies_to_variant_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "override_price_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "override_discount_percent": {
            "type": [
              "integer",
              "null"
            ]
          },
          "starts_at": {
            "type": "string"
          },
          "ends_at": {
            "type": "string"
          },
          "max_redemptions": {
            "type": [
              "integer",
              "null"
            ]
          },
          "current_redemptions": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "description",
          "applies_to_plan_group",
          "applies_to_billing_period",
          "applies_to_variant_label",
          "override_price_minor",
          "override_discount_percent",
          "starts_at",
          "ends_at",
          "max_redemptions",
          "current_redemptions",
          "status",
          "is_active",
          "created_at",
          "updated_at"
        ]
      },
      "AdminPricingCampaignCreate": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "applies_to_plan_group": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80
          },
          "applies_to_billing_period": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "applies_to_variant_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "override_price_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "override_discount_percent": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 99
          },
          "starts_at": {
            "type": "string"
          },
          "ends_at": {
            "type": "string"
          },
          "max_redemptions": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "status": {
            "type": "string",
            "default": "planning"
          },
          "is_active": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "code",
          "name",
          "starts_at",
          "ends_at"
        ]
      },
      "AdminPricingCampaignUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "applies_to_plan_group": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80
          },
          "applies_to_billing_period": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20
          },
          "applies_to_variant_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "override_price_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "override_discount_percent": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 99
          },
          "starts_at": {
            "type": "string"
          },
          "ends_at": {
            "type": "string"
          },
          "max_redemptions": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "status": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          }
        }
      },
      "AdminMembershipOverview": {
        "type": "object",
        "properties": {
          "free_users": {
            "type": "integer"
          },
          "premium_users": {
            "type": "integer"
          },
          "trial_users": {
            "type": "integer"
          },
          "past_due_users": {
            "type": "integer"
          },
          "cancel_scheduled": {
            "type": "integer"
          }
        },
        "required": [
          "free_users",
          "premium_users",
          "trial_users",
          "past_due_users",
          "cancel_scheduled"
        ]
      },
      "AdminVariantPerformance": {
        "type": "object",
        "properties": {
          "variant_label": {
            "type": "string"
          },
          "billing_period": {
            "type": "string"
          },
          "active_count": {
            "type": "integer"
          },
          "trial_count": {
            "type": "integer"
          }
        },
        "required": [
          "variant_label",
          "billing_period",
          "active_count",
          "trial_count"
        ]
      },
      "AdminTrialFunnel": {
        "type": "object",
        "properties": {
          "trial_started_30d": {
            "type": "integer"
          },
          "trial_converted_30d": {
            "type": "integer"
          },
          "cancelled_30d": {
            "type": "integer"
          }
        },
        "required": [
          "trial_started_30d",
          "trial_converted_30d",
          "cancelled_30d"
        ]
      },
      "AdminMembershipDashboard": {
        "type": "object",
        "properties": {
          "overview": {
            "$ref": "#/components/schemas/AdminMembershipOverview"
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminVariantPerformance"
            }
          },
          "trial_funnel": {
            "$ref": "#/components/schemas/AdminTrialFunnel"
          }
        },
        "required": [
          "overview",
          "variants",
          "trial_funnel"
        ]
      },
      "StorePlatform": {
        "type": "string",
        "enum": [
          "play_store",
          "app_store"
        ]
      },
      "StoreConnectionStatus": {
        "type": "string",
        "enum": [
          "not_configured",
          "connected",
          "error"
        ]
      },
      "StoreIntegration": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "store": {
            "$ref": "#/components/schemas/StorePlatform"
          },
          "display_name": {
            "type": "string"
          },
          "package_or_app_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "vendor_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "connection_status": {
            "$ref": "#/components/schemas/StoreConnectionStatus"
          },
          "last_status_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "store",
          "display_name",
          "package_or_app_id",
          "vendor_number",
          "connection_status",
          "last_status_message",
          "last_synced_at",
          "last_sync_error"
        ]
      },
      "StoreSalesRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "store": {
            "type": "string"
          },
          "report_date": {
            "type": "string"
          },
          "product_id": {
            "type": "string"
          },
          "country_code": {
            "type": "string"
          },
          "units": {
            "type": "integer"
          },
          "proceeds_minor": {
            "type": "integer"
          },
          "gross_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "fetched_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "store",
          "report_date",
          "product_id",
          "country_code",
          "units",
          "proceeds_minor",
          "gross_minor",
          "currency",
          "fetched_at"
        ]
      },
      "StoreMetricsRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "store": {
            "type": "string"
          },
          "metric_date": {
            "type": "string"
          },
          "installs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "uninstalls": {
            "type": [
              "integer",
              "null"
            ]
          },
          "active_devices": {
            "type": [
              "integer",
              "null"
            ]
          },
          "impressions": {
            "type": [
              "integer",
              "null"
            ]
          },
          "product_page_views": {
            "type": [
              "integer",
              "null"
            ]
          },
          "crash_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "anr_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "rating_average": {
            "type": [
              "number",
              "null"
            ]
          },
          "rating_count": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "store",
          "metric_date",
          "installs",
          "uninstalls",
          "active_devices",
          "impressions",
          "product_page_views",
          "crash_rate",
          "anr_rate",
          "rating_average",
          "rating_count"
        ]
      },
      "StoreReview": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "store": {
            "type": "string"
          },
          "external_review_id": {
            "type": "string"
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating": {
            "type": [
              "integer",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "reply_body": {
            "type": [
              "string",
              "null"
            ]
          },
          "reply_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "posted_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "store",
          "external_review_id",
          "author_name",
          "rating",
          "title",
          "body",
          "reply_body",
          "reply_at",
          "posted_at"
        ]
      },
      "StoreSyncTask": {
        "type": "string",
        "enum": [
          "sales",
          "metrics",
          "reviews"
        ]
      },
      "StoreSyncStatus": {
        "type": "string",
        "enum": [
          "queued",
          "started",
          "success",
          "partial",
          "error",
          "skipped_not_configured"
        ]
      },
      "StoreSyncRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "store": {
            "$ref": "#/components/schemas/StorePlatform"
          },
          "task": {
            "$ref": "#/components/schemas/StoreSyncTask"
          },
          "status": {
            "$ref": "#/components/schemas/StoreSyncStatus"
          },
          "started_at": {
            "type": "string"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "rows_upserted": {
            "type": "integer"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "triggered_by": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "store",
          "task",
          "status",
          "started_at",
          "finished_at",
          "rows_upserted",
          "message",
          "triggered_by"
        ]
      },
      "BadgeDefinition": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "badge_code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "icon": {
            "type": "string"
          },
          "activity_type": {
            "type": "string"
          },
          "threshold": {
            "type": "integer"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conditions": {},
          "category": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "holders_count": {
            "type": "integer"
          },
          "asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "badge_code",
          "name",
          "description",
          "icon",
          "activity_type",
          "threshold",
          "sort_order",
          "is_active",
          "asset_id",
          "category",
          "created_at"
        ]
      },
      "BadgeDefinitionUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "icon": {
            "type": "string"
          },
          "activity_type": {
            "type": "string"
          },
          "threshold": {
            "type": "integer"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conditions": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "category": {
            "type": "string"
          }
        }
      },
      "ActivityExpRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "activity_type": {
            "type": "string"
          },
          "exp_amount": {
            "type": "integer"
          },
          "description": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "activity_type",
          "exp_amount",
          "description",
          "is_active",
          "created_at",
          "updated_at"
        ]
      },
      "AdminExpRuleUpdate": {
        "type": "object",
        "properties": {
          "activity_type": {
            "type": "string"
          },
          "exp_amount": {
            "type": "integer"
          },
          "is_active": {
            "type": "boolean"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "LevelDefinition": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "level": {
            "type": "integer"
          },
          "required_exp": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "level",
          "required_exp",
          "created_at"
        ]
      },
      "GamificationAnomalies": {
        "type": "object",
        "properties": {
          "daily_cap_hits": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "hour_bucket": {
                  "type": "string"
                },
                "activity_type": {
                  "type": "string"
                },
                "cap_hits": {
                  "type": "integer"
                },
                "total_attempts": {
                  "type": "integer"
                },
                "hit_rate_pct": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "required": [
                "hour_bucket",
                "activity_type",
                "cap_hits",
                "total_attempts",
                "hit_rate_pct"
              ]
            }
          },
          "geo_unverified": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "hour_bucket": {
                  "type": "string"
                },
                "activity_type": {
                  "type": "string"
                },
                "unverified_count": {
                  "type": "integer"
                },
                "verified_count": {
                  "type": "integer"
                },
                "null_count": {
                  "type": "integer"
                },
                "unverified_rate_pct": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "required": [
                "hour_bucket",
                "activity_type",
                "unverified_count",
                "verified_count",
                "null_count",
                "unverified_rate_pct"
              ]
            }
          },
          "top_exp_users_24h": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "user_id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "display_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "exp_granted_24h": {
                  "type": "integer"
                },
                "activity_count_24h": {
                  "type": "integer"
                },
                "cap_hits_24h": {
                  "type": "integer"
                },
                "geo_unverified_24h": {
                  "type": "integer"
                },
                "last_activity_at": {
                  "type": "string"
                }
              },
              "required": [
                "user_id",
                "display_name",
                "email",
                "exp_granted_24h",
                "activity_count_24h",
                "cap_hits_24h",
                "geo_unverified_24h",
                "last_activity_at"
              ]
            }
          },
          "pending_referrals": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "referrer_user_id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "referrer_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "referee_user_id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "referee_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "referee_signup_at": {
                  "type": "string"
                },
                "applied_at": {
                  "type": "string"
                },
                "referee_device_fingerprint": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "referee_lifetime_amount_minor": {
                  "type": "integer"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "confirmed",
                    "rejected",
                    "pending"
                  ]
                }
              },
              "required": [
                "id",
                "referrer_user_id",
                "referrer_name",
                "referee_user_id",
                "referee_name",
                "referee_signup_at",
                "applied_at",
                "referee_device_fingerprint",
                "referee_lifetime_amount_minor",
                "status"
              ]
            }
          }
        },
        "required": [
          "daily_cap_hits",
          "geo_unverified",
          "top_exp_users_24h",
          "pending_referrals"
        ]
      },
      "AdminTheme": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "theme_code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_free": {
            "type": "boolean"
          },
          "price_yen_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "preview_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "preview_asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          },
          "owners_count": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "theme_id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "category": {
                  "type": "string"
                },
                "part_id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                }
              },
              "required": [
                "id",
                "theme_id",
                "category",
                "part_id"
              ]
            }
          }
        },
        "required": [
          "id",
          "theme_code",
          "name",
          "description",
          "is_free",
          "price_yen_minor",
          "preview_asset_id",
          "is_active",
          "sort_order",
          "created_at",
          "updated_at"
        ]
      },
      "AdminThemeUpdate": {
        "type": "object",
        "properties": {
          "theme_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[a-z0-9_]+$"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_free": {
            "type": "boolean"
          },
          "price_yen_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "preview_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          }
        }
      },
      "AdminThemePart": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string"
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "accent_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "pin_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "icon_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "loading_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "loading_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          },
          "used_in_themes_count": {
            "type": "integer"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pin_asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          },
          "icon_asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          },
          "loading_asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "category",
          "primary_color",
          "accent_color",
          "pin_asset_id",
          "icon_asset_id",
          "loading_asset_id",
          "loading_type",
          "is_active",
          "sort_order"
        ]
      },
      "AdminThemePartUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string"
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "accent_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "pin_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "icon_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "loading_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "loading_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          }
        }
      },
      "AiProvider": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "provider_key": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "model_name": {
            "type": "string"
          },
          "vault_secret_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "is_enabled": {
            "type": "boolean"
          },
          "priority": {
            "type": "integer"
          },
          "config": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "provider_key",
          "display_name",
          "model_name",
          "vault_secret_id",
          "is_enabled",
          "priority",
          "config",
          "created_at",
          "updated_at"
        ]
      },
      "AiUsageLog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "provider_key": {
            "type": "string"
          },
          "user_input": {
            "type": "string"
          },
          "parsed_query": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "response_status": {
            "type": "string"
          },
          "attempt_number": {
            "type": "integer"
          },
          "fallback_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "error_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "provider_key",
          "user_input",
          "parsed_query",
          "response_status",
          "attempt_number",
          "fallback_reason",
          "error_code",
          "error_message",
          "session_id",
          "created_at"
        ]
      },
      "AdminCode": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "category_id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "display_label": {
            "type": "string"
          },
          "lang": {
            "type": "string"
          },
          "sort_order": {
            "type": [
              "integer",
              "null"
            ]
          },
          "is_deleted": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "category_id",
          "code",
          "display_label",
          "lang",
          "sort_order",
          "is_deleted"
        ]
      },
      "CodeUpdate": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_deleted": {
            "type": "boolean"
          }
        }
      },
      "SyncMeta": {
        "type": "object",
        "properties": {
          "synced": {
            "type": "boolean"
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "enum": [
              "db",
              "stub",
              "ga4",
              "x_api",
              "sns_snapshot",
              "mixed"
            ]
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "synced",
          "synced_at",
          "source"
        ],
        "description": "response の同期ステータス envelope。feature flag / 0 埋め固定値を統一する。"
      },
      "ParkingSessionRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "parking_lot_id",
          "status",
          "created_at"
        ]
      },
      "AdminParkingSession": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ParkingSessionRow"
          },
          {
            "type": "object",
            "properties": {
              "parking_spot_id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "started_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ended_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "total_amount_minor": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "memo": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "parking_spot_id",
              "started_at",
              "ended_at",
              "total_amount_minor"
            ]
          }
        ]
      },
      "AdminParkingSessionWithRelations": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdminParkingSession"
          },
          {
            "type": "object",
            "properties": {
              "user": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "display_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "display_name",
                  "email"
                ]
              },
              "parking_lot": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "name": {
                    "type": "string"
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name",
                  "address"
                ]
              },
              "parking_spot": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "code": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "code"
                ]
              }
            },
            "required": [
              "user",
              "parking_lot",
              "parking_spot"
            ]
          }
        ]
      },
      "AdminParkingSessionUpdate": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "parking_spot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "total_amount_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "IgSlideCategory": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "prefix": {
            "type": [
              "string",
              "null"
            ]
          },
          "sort_order": {
            "type": "integer"
          },
          "is_deleted": {
            "type": "integer"
          }
        },
        "required": [
          "code",
          "label",
          "prefix",
          "sort_order",
          "is_deleted"
        ]
      },
      "IgPostCategory": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "sort_order": {
            "type": "integer"
          },
          "is_deleted": {
            "type": "integer"
          }
        },
        "required": [
          "code",
          "label",
          "color",
          "sort_order",
          "is_deleted"
        ]
      },
      "IgTag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "color",
          "usage_count"
        ]
      },
      "IgTemplate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slide_type": {
            "type": "string"
          },
          "html_body": {
            "type": "string"
          },
          "slot_schema": {
            "type": "string"
          },
          "sample_content": {
            "type": "string",
            "default": "{}"
          },
          "sample_html": {
            "type": "string",
            "default": ""
          },
          "uses_parking_lot": {
            "type": "integer",
            "default": 0
          },
          "genre": {
            "type": "string",
            "enum": [
              "parking",
              "useful_info"
            ],
            "default": "parking"
          },
          "sort_order": {
            "type": "integer"
          },
          "is_active": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "slide_type",
          "html_body",
          "slot_schema",
          "sort_order",
          "is_active"
        ]
      },
      "IgPostTemplate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "slide_refs": {
            "type": "string"
          },
          "genre": {
            "type": "string",
            "enum": [
              "parking",
              "useful_info"
            ],
            "default": "parking"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "description",
          "slide_refs"
        ]
      },
      "IgSlide": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "campaign_id": {
            "type": "string"
          },
          "template_id": {
            "type": "string"
          },
          "slide_index": {
            "type": "integer"
          },
          "content": {
            "type": "string"
          },
          "html_override": {
            "type": [
              "string",
              "null"
            ]
          },
          "png_r2_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "png_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "revision_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "parking_lot_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "campaign_id",
          "template_id",
          "slide_index",
          "content",
          "html_override",
          "png_r2_key",
          "png_url",
          "revision_notes",
          "created_at",
          "updated_at"
        ]
      },
      "IgCampaign": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "theme": {
            "type": [
              "string",
              "null"
            ]
          },
          "area": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "ig_media_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_material": {
            "type": [
              "string",
              "null"
            ]
          },
          "post_category_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "genre": {
            "type": "string",
            "enum": [
              "parking",
              "useful_info"
            ],
            "default": "parking"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "code",
          "title",
          "theme",
          "area",
          "status",
          "scheduled_at",
          "ig_media_id",
          "notes",
          "source_material",
          "post_category_code",
          "created_by",
          "created_at",
          "updated_at"
        ]
      },
      "IgCaption": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "campaign_id": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "hashtags": {
            "type": [
              "string",
              "null"
            ]
          },
          "draft_body": {
            "type": [
              "string",
              "null"
            ]
          },
          "generated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "campaign_id",
          "body",
          "hashtags",
          "draft_body",
          "generated_at",
          "created_at",
          "updated_at"
        ]
      },
      "IgCompetitorSnapshot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "campaign_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "raw_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "ai_ideas": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "campaign_id",
          "source_url",
          "account_handle",
          "raw_notes",
          "ai_ideas",
          "created_by",
          "created_at"
        ]
      },
      "AdminAsset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "category": {
            "type": "string"
          },
          "s3_key": {
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "mime_type": {
            "type": "string"
          },
          "file_size": {
            "type": "integer"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "category",
          "s3_key",
          "file_name",
          "mime_type",
          "file_size",
          "width",
          "height",
          "created_at"
        ]
      },
      "AdminAssetUpdate": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "mime_type": {
            "type": "string"
          },
          "file_size": {
            "type": "integer"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "AdminUserVehicle": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "nickname": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_type_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "maker_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "size_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "color_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "year": {
            "type": [
              "integer",
              "null"
            ]
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "is_primary": {
            "type": "boolean"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "user": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "display_name"
            ]
          },
          "asset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "s3_key": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "mime_type": {
                "type": "string"
              },
              "category": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "s3_key",
              "file_name",
              "mime_type",
              "category"
            ]
          }
        },
        "required": [
          "id",
          "user_id",
          "nickname",
          "vehicle_type_code",
          "maker_code",
          "size_code",
          "color_code",
          "model_name",
          "year",
          "asset_id",
          "is_primary",
          "notes",
          "deleted_at",
          "created_at",
          "updated_at"
        ]
      },
      "UserVehicleUpdate": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "nickname": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_type_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "maker_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "size_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "color_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "model_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "year": {
            "type": [
              "integer",
              "null"
            ]
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "is_primary": {
            "type": "boolean"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "SearchQueryV1": {
        "type": "object",
        "properties": {
          "v": {
            "type": "number",
            "enum": [
              1
            ],
            "description": "スキーマバージョン",
            "examples": [
              1
            ]
          },
          "center": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number",
                "minimum": -90,
                "maximum": 90
              },
              "lng": {
                "type": "number",
                "minimum": -180,
                "maximum": 180
              },
              "placeName": {
                "type": "string",
                "maxLength": 200
              }
            },
            "required": [
              "lat",
              "lng"
            ]
          },
          "radius_m": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 50000
          },
          "price_min": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100000
          },
          "price_max": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100000
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "covered",
                "open_24h",
                "entry_24h",
                "ev_charging",
                "oversized_ok",
                "motorcycle_ok",
                "wheelchair_accessible",
                "barrier_free",
                "reservable",
                "security_camera",
                "has_max_fee",
                "monthly_available",
                "coin_500_or_less",
                "near_station",
                "low_price",
                "partner_facility",
                "24h",
                "max_fee"
              ]
            },
            "maxItems": 20
          },
          "difficulty": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "easy",
                "normal",
                "hard"
              ]
            },
            "maxItems": 3
          },
          "operator_codes": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            },
            "maxItems": 50
          },
          "vehicle": {
            "type": "object",
            "properties": {
              "height_m": {
                "type": [
                  "number",
                  "null"
                ],
                "exclusiveMinimum": 0,
                "maximum": 10
              },
              "width_m": {
                "type": [
                  "number",
                  "null"
                ],
                "exclusiveMinimum": 0,
                "maximum": 5
              },
              "length_m": {
                "type": [
                  "number",
                  "null"
                ],
                "exclusiveMinimum": 0,
                "maximum": 20
              },
              "weight_t": {
                "type": [
                  "number",
                  "null"
                ],
                "exclusiveMinimum": 0,
                "maximum": 50
              },
              "clearance_cm": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "maximum": 100
              },
              "tire_width_mm": {
                "type": [
                  "integer",
                  "null"
                ],
                "exclusiveMinimum": 0,
                "maximum": 500
              }
            }
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "maxItems": 10
          },
          "meter_ticket_included": {
            "type": "boolean"
          },
          "sort": {
            "type": "string",
            "enum": [
              "distance",
              "price",
              "recommended"
            ]
          }
        },
        "additionalProperties": false
      },
      "AdminUserSearchPreset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "query_json": {
            "$ref": "#/components/schemas/SearchQueryV1"
          },
          "is_default": {
            "type": "boolean"
          },
          "sort_order": {
            "type": "integer"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "user": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "display_name"
            ]
          }
        },
        "required": [
          "id",
          "user_id",
          "name",
          "query_json",
          "is_default",
          "sort_order",
          "deleted_at",
          "created_at",
          "updated_at"
        ]
      },
      "PlacesImportLotResult": {
        "type": "object",
        "properties": {
          "lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "fetched": {
            "type": "integer"
          },
          "upserted": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "auto_on_map": {
            "type": "integer"
          },
          "auto_on_facility": {
            "type": "integer"
          }
        },
        "required": [
          "lot_id",
          "fetched",
          "upserted",
          "skipped",
          "auto_on_map",
          "auto_on_facility"
        ]
      },
      "PlacesImportAllResult": {
        "type": "object",
        "properties": {
          "total_lots": {
            "type": "integer"
          },
          "processed": {
            "type": "integer"
          },
          "total_upserted": {
            "type": "integer"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "lot_id": {
                  "type": "string",
                  "format": "uuid",
                  "examples": [
                    "00000000-0000-0000-0000-000000000000"
                  ]
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "lot_id",
                "message"
              ]
            }
          },
          "dry_run": {
            "type": "boolean"
          }
        },
        "required": [
          "total_lots",
          "processed",
          "total_upserted",
          "errors",
          "dry_run"
        ]
      },
      "AdminAreaPlace": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "banner_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "radius_m": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "is_sponsored": {
            "type": "boolean"
          },
          "source": {
            "type": "string"
          },
          "source_place_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating": {
            "type": [
              "number",
              "null"
            ]
          },
          "user_ratings_total": {
            "type": [
              "integer",
              "null"
            ]
          },
          "place_types": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "show_on_map": {
            "type": "boolean"
          },
          "show_as_facility": {
            "type": "boolean"
          },
          "last_refreshed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "category",
          "lat",
          "lng",
          "radius_m",
          "status",
          "is_sponsored",
          "source",
          "show_on_map",
          "show_as_facility",
          "created_at",
          "updated_at"
        ]
      },
      "OsmBbox": {
        "type": "object",
        "properties": {
          "south": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "examples": [
              35.65
            ]
          },
          "west": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "examples": [
              139.73
            ]
          },
          "north": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "examples": [
              35.7
            ]
          },
          "east": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "examples": [
              139.78
            ]
          }
        },
        "required": [
          "south",
          "west",
          "north",
          "east"
        ]
      },
      "AdminOsmIngestResult": {
        "type": "object",
        "properties": {
          "run_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_ms": {
            "type": "integer"
          },
          "bbox": {
            "$ref": "#/components/schemas/OsmBbox"
          },
          "bbox_source": {
            "type": "string",
            "enum": [
              "request",
              "app_config"
            ]
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ]
          },
          "generated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              },
              "matched": {
                "type": "integer"
              },
              "unmatched": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              },
              "payload_created": {
                "type": "integer"
              },
              "payload_updated": {
                "type": "integer"
              },
              "payload_unchanged": {
                "type": "integer"
              },
              "fields_inserted": {
                "type": "integer"
              }
            },
            "required": [
              "total",
              "skipped",
              "matched",
              "unmatched",
              "errors",
              "payload_created",
              "payload_updated",
              "payload_unchanged",
              "fields_inserted"
            ]
          },
          "dry_run": {
            "type": "boolean"
          }
        },
        "required": [
          "run_id",
          "duration_ms",
          "bbox",
          "bbox_source",
          "endpoint",
          "generated_at",
          "last_error",
          "summary",
          "dry_run"
        ]
      },
      "AdminOsmIngestRequest": {
        "type": "object",
        "properties": {
          "bbox": {
            "$ref": "#/components/schemas/OsmBbox"
          },
          "dry_run": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "GoogleIngestBbox": {
        "type": "object",
        "properties": {
          "south": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "examples": [
              35.65
            ]
          },
          "west": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "examples": [
              139.73
            ]
          },
          "north": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "examples": [
              35.7
            ]
          },
          "east": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "examples": [
              139.78
            ]
          }
        },
        "required": [
          "south",
          "west",
          "north",
          "east"
        ]
      },
      "AdminGoogleIngestResult": {
        "type": "object",
        "properties": {
          "run_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_ms": {
            "type": "integer"
          },
          "bbox": {
            "$ref": "#/components/schemas/GoogleIngestBbox"
          },
          "bbox_source": {
            "type": "string",
            "enum": [
              "request",
              "app_config"
            ]
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ]
          },
          "generated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              },
              "matched": {
                "type": "integer"
              },
              "unmatched": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              },
              "payload_created": {
                "type": "integer"
              },
              "payload_updated": {
                "type": "integer"
              },
              "payload_unchanged": {
                "type": "integer"
              },
              "fields_inserted": {
                "type": "integer"
              }
            },
            "required": [
              "total",
              "skipped",
              "matched",
              "unmatched",
              "errors",
              "payload_created",
              "payload_updated",
              "payload_unchanged",
              "fields_inserted"
            ]
          },
          "dry_run": {
            "type": "boolean"
          }
        },
        "required": [
          "run_id",
          "duration_ms",
          "bbox",
          "bbox_source",
          "endpoint",
          "generated_at",
          "last_error",
          "summary",
          "dry_run"
        ]
      },
      "AdminGoogleIngestRequest": {
        "type": "object",
        "properties": {
          "bbox": {
            "$ref": "#/components/schemas/GoogleIngestBbox"
          },
          "dry_run": {
            "type": "boolean",
            "default": false
          },
          "max_api_calls": {
            "type": "integer",
            "minimum": 1,
            "maximum": 500
          }
        }
      },
      "AdminParkingModerationSummary": {
        "type": "object",
        "properties": {
          "pending_lots": {
            "type": "integer"
          },
          "pending_fields": {
            "type": "integer"
          },
          "unmatched_payloads": {
            "type": "integer"
          }
        },
        "required": [
          "pending_lots",
          "pending_fields",
          "unmatched_payloads"
        ]
      },
      "AdminParkingModerationPendingLot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "raw_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_spaces": {
            "type": [
              "integer",
              "null"
            ]
          },
          "pricing_rules_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "reverted_from_denials_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "pending_origin": {
            "type": "string",
            "enum": [
              "reverted",
              "promote_new",
              "auto_ingest"
            ]
          }
        },
        "required": [
          "id",
          "code",
          "source",
          "status",
          "lat",
          "lng",
          "raw_text",
          "name",
          "address",
          "total_spaces",
          "pricing_rules_count",
          "created_at",
          "updated_at",
          "reverted_from_denials_at",
          "pending_origin"
        ]
      },
      "AdminParkingModerationPendingLotsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationPendingLot"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset"
        ]
      },
      "AdminParkingModerationApproveResult": {
        "type": "object",
        "properties": {
          "updated": {
            "type": "integer"
          },
          "fields_approved": {
            "type": "integer"
          }
        },
        "required": [
          "updated",
          "fields_approved"
        ]
      },
      "AdminParkingModerationRejectBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "AdminParkingModerationBulkBody": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "minItems": 1,
            "maxItems": 200
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "ids"
        ]
      },
      "AdminParkingModerationPendingFieldValue": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "lot_code": {
            "type": "string"
          },
          "lot_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_status": {
            "type": "string"
          },
          "field_name": {
            "type": "string"
          },
          "value": {},
          "source": {
            "type": "string"
          },
          "contributor_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "captured_at": {
            "type": "string"
          },
          "confirms_n": {
            "type": "integer"
          },
          "priority_score": {
            "type": "number"
          },
          "is_primary": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "parking_lot_id",
          "lot_code",
          "lot_name",
          "lot_address",
          "lot_status",
          "field_name",
          "source",
          "contributor_id",
          "captured_at",
          "confirms_n",
          "priority_score",
          "is_primary",
          "created_at"
        ]
      },
      "AdminParkingModerationPendingFieldValuesList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationPendingFieldValue"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset"
        ]
      },
      "AdminParkingModerationReasonBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "AdminParkingModerationFieldValueBulkApproveBody": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "minItems": 1,
            "maxItems": 200
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "ids"
        ]
      },
      "AdminParkingModerationUgcCandidate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdminParkingModerationPendingFieldValue"
          },
          {
            "type": "object",
            "properties": {
              "current_primary_value": {},
              "current_primary_source": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "current_primary_source"
            ]
          }
        ]
      },
      "AdminParkingModerationUgcCandidatesList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationUgcCandidate"
            }
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "min_confirms_n": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "limit",
          "offset",
          "min_confirms_n"
        ]
      },
      "AdminParkingModerationUnmatchedPayload": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string"
          },
          "external_id": {
            "type": "string"
          },
          "source_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "captured_at": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "extracted_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "extracted_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "nearby_lot_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "source",
          "external_id",
          "source_version",
          "captured_at",
          "created_at",
          "lat",
          "lng",
          "extracted_name",
          "extracted_address",
          "nearby_lot_count"
        ]
      },
      "AdminParkingModerationUnmatchedPayloadsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationUnmatchedPayload"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "source_counts": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset",
          "source_counts"
        ]
      },
      "AdminParkingModerationLinkBody": {
        "type": "object",
        "properties": {
          "parking_lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "parking_lot_id"
        ]
      },
      "AdminParkingModerationPayloadReasonBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "AdminParkingModerationPayloadBulkRejectBody": {
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "minItems": 1,
            "maxItems": 200
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "ids"
        ]
      },
      "AdminParkingModerationNearbyLotShort": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "distance_m": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "distance_m"
        ]
      },
      "AdminParkingModerationUnmatchedPayloadDetail": {
        "type": "object",
        "properties": {
          "payload": {},
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "nearby_lots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationNearbyLotShort"
            }
          }
        },
        "required": [
          "lat",
          "lng",
          "nearby_lots"
        ]
      },
      "AdminParkingModerationNearbyLot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "raw_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "distance_m": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "code",
          "status",
          "source",
          "lat",
          "lng",
          "raw_text",
          "distance_m"
        ]
      },
      "AdminParkingModerationNearbyLotsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationNearbyLot"
            }
          },
          "payload_lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "payload_lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "radius_m": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "payload_lat",
          "payload_lng",
          "radius_m"
        ]
      },
      "AdminParkingModerationDenialItem": {
        "type": "object",
        "properties": {
          "field_value_id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "lot_code": {
            "type": "string"
          },
          "lot_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "field_name": {
            "type": "string"
          },
          "current_value": {},
          "source": {
            "type": "string"
          },
          "is_primary": {
            "type": "boolean"
          },
          "denials_count": {
            "type": "integer"
          },
          "count_since": {
            "type": "string"
          },
          "latest_denial_at": {
            "type": "string"
          }
        },
        "required": [
          "field_value_id",
          "parking_lot_id",
          "lot_code",
          "lot_name",
          "lot_address",
          "field_name",
          "source",
          "is_primary",
          "denials_count",
          "count_since",
          "latest_denial_at"
        ]
      },
      "AdminParkingModerationDenialsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationDenialItem"
            }
          },
          "window_days": {
            "type": "integer"
          },
          "min_count": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "window_days",
          "min_count"
        ]
      },
      "AdminParkingModerationDenialReasonBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "AdminParkingModerationFeedbackItem": {
        "type": "object",
        "properties": {
          "primary_id": {
            "type": "string",
            "format": "uuid"
          },
          "parking_lot_id": {
            "type": "string",
            "format": "uuid"
          },
          "lot_code": {
            "type": "string"
          },
          "lot_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "lot_status": {
            "type": "string"
          },
          "field_name": {
            "type": "string"
          },
          "primary_value": {},
          "primary_source": {
            "type": "string"
          },
          "confirms_count": {
            "type": "integer"
          },
          "denials_count": {
            "type": "integer"
          },
          "latest_tap_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "ugc_candidate_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "ugc_candidate_value": {},
          "ugc_candidate_confirms_n": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "primary_id",
          "parking_lot_id",
          "lot_code",
          "lot_name",
          "lot_address",
          "lot_status",
          "field_name",
          "primary_source",
          "confirms_count",
          "denials_count",
          "latest_tap_at",
          "ugc_candidate_id",
          "ugc_candidate_confirms_n"
        ]
      },
      "AdminParkingModerationFeedbackList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationFeedbackItem"
            }
          },
          "window_days": {
            "type": "integer"
          },
          "ugc_min_confirms_n": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "window_days",
          "ugc_min_confirms_n"
        ]
      },
      "AdminParkingModerationIngestScheduleItem": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "field",
              "ugc",
              "akippa",
              "navitime",
              "google",
              "osm",
              "kokudo",
              "jichitai",
              "import"
            ]
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "interval_days": {
            "type": "integer"
          },
          "bbox": {
            "type": "object",
            "properties": {
              "south": {
                "type": "number"
              },
              "west": {
                "type": "number"
              },
              "north": {
                "type": "number"
              },
              "east": {
                "type": "number"
              }
            },
            "required": [
              "south",
              "west",
              "north",
              "east"
            ]
          },
          "max_api_calls": {
            "type": [
              "integer",
              "null"
            ]
          },
          "last_run_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_run_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ok",
              "error",
              "partial"
            ]
          },
          "last_run_summary": {},
          "next_run_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": "string"
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        },
        "required": [
          "source",
          "name",
          "enabled",
          "interval_days",
          "bbox",
          "max_api_calls",
          "last_run_at",
          "last_run_status",
          "next_run_at",
          "updated_at",
          "updated_by"
        ]
      },
      "AdminParkingModerationIngestSchedulesList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleItem"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "AdminParkingModerationIngestScheduleUpsertBody": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "interval_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365
          },
          "bbox": {
            "type": "object",
            "properties": {
              "south": {
                "type": "number"
              },
              "west": {
                "type": "number"
              },
              "north": {
                "type": "number"
              },
              "east": {
                "type": "number"
              }
            },
            "required": [
              "south",
              "west",
              "north",
              "east"
            ]
          },
          "max_api_calls": {
            "type": [
              "integer",
              "null"
            ],
            "exclusiveMinimum": 0
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "enabled",
          "interval_days",
          "bbox"
        ]
      },
      "AdminParkingModerationIngestScheduleDuplicateBody": {
        "type": "object",
        "properties": {
          "to_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "to_name"
        ]
      },
      "AdminParkingModerationIngestScheduleRunNowResult": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "field",
              "ugc",
              "akippa",
              "navitime",
              "google",
              "osm",
              "kokudo",
              "jichitai",
              "import"
            ]
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "error",
              "partial"
            ]
          },
          "run_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_ms": {
            "type": "integer"
          },
          "endpoint": {
            "type": [
              "string",
              "null"
            ]
          },
          "generated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              },
              "matched": {
                "type": "integer"
              },
              "unmatched": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              },
              "payloadCreated": {
                "type": "integer"
              },
              "payloadUpdated": {
                "type": "integer"
              },
              "payloadUnchanged": {
                "type": "integer"
              },
              "fieldsInserted": {
                "type": "integer"
              }
            },
            "required": [
              "total",
              "skipped",
              "matched",
              "unmatched",
              "errors",
              "payloadCreated",
              "payloadUpdated",
              "payloadUnchanged",
              "fieldsInserted"
            ]
          }
        },
        "required": [
          "source",
          "name",
          "status",
          "run_id",
          "duration_ms",
          "endpoint",
          "generated_at",
          "last_error",
          "summary"
        ]
      },
      "AdminParkingModerationIngestScheduleRunNowBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        }
      },
      "AdminParkingModerationIngestRunSummary": {
        "type": "object",
        "properties": {
          "run_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string"
          },
          "triggered_by": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "error_summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_label": {
            "type": "string",
            "enum": [
              "成功",
              "部分成功",
              "失敗",
              "実行中"
            ]
          },
          "payloads_fetched": {
            "type": "integer"
          },
          "matched_at_ingest": {
            "type": "integer"
          },
          "unmatched_at_ingest": {
            "type": "integer"
          },
          "skipped_at_ingest": {
            "type": "integer"
          },
          "errors_count": {
            "type": "integer"
          },
          "currently_linked": {
            "type": "integer"
          },
          "currently_pending": {
            "type": "integer"
          },
          "currently_rejected": {
            "type": "integer"
          }
        },
        "required": [
          "run_id",
          "source",
          "triggered_by",
          "started_at",
          "finished_at",
          "duration_ms",
          "error_summary",
          "status_label",
          "payloads_fetched",
          "matched_at_ingest",
          "unmatched_at_ingest",
          "skipped_at_ingest",
          "errors_count",
          "currently_linked",
          "currently_pending",
          "currently_rejected"
        ]
      },
      "AdminParkingModerationIngestRunsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationIngestRunSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "window_days": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total",
          "window_days",
          "limit",
          "offset"
        ]
      },
      "AdminParkingModerationIngestRunPayloadDetail": {
        "type": "object",
        "properties": {
          "payload_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string"
          },
          "external_id": {
            "type": "string"
          },
          "captured_at": {
            "type": "string"
          },
          "current_state": {
            "type": "string",
            "enum": [
              "linked",
              "pending",
              "rejected"
            ]
          },
          "parking_lot_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "linked_lot_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason_summary": {
            "type": "string"
          }
        },
        "required": [
          "payload_id",
          "source",
          "external_id",
          "captured_at",
          "current_state",
          "parking_lot_id",
          "linked_lot_code",
          "reason_summary"
        ]
      },
      "AdminParkingModerationIngestRunPayloadsList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminParkingModerationIngestRunPayloadDetail"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset"
        ]
      },
      "AdminPlaceDiscount": {
        "type": "object",
        "properties": {
          "place_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "lot_id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "terms": {
            "type": [
              "string",
              "null"
            ]
          },
          "valid_from": {
            "type": [
              "string",
              "null"
            ]
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "place_name": {
            "type": "string"
          },
          "lot_name": {
            "type": "string"
          }
        },
        "required": [
          "place_id",
          "lot_id",
          "title",
          "created_at",
          "updated_at"
        ]
      },
      "AdminAppConfig": {
        "type": "object",
        "properties": {
          "min_app_version_ios": {
            "type": "string",
            "examples": [
              "1.0.0"
            ]
          },
          "min_app_version_android": {
            "type": "string",
            "examples": [
              "1.0.0"
            ]
          },
          "is_maintenance": {
            "type": "boolean",
            "examples": [
              false
            ]
          },
          "maintenance_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "store_url_ios": {
            "type": [
              "string",
              "null"
            ]
          },
          "store_url_android": {
            "type": [
              "string",
              "null"
            ]
          },
          "walking_cost_yen_per_minute": {
            "type": "integer",
            "minimum": 0,
            "maximum": 500,
            "description": "検索おすすめ順の既定: 徒歩1分あたり何円と換算するか（0〜500）",
            "examples": [
              17
            ]
          },
          "updated_at": {
            "type": "string"
          },
          "updated_by_admin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          }
        },
        "required": [
          "min_app_version_ios",
          "min_app_version_android",
          "is_maintenance",
          "maintenance_message",
          "store_url_ios",
          "store_url_android",
          "walking_cost_yen_per_minute",
          "updated_at",
          "updated_by_admin_id"
        ]
      },
      "AppConfigHistoryItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "value": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "changed_at": {
            "type": "string"
          },
          "changed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          }
        },
        "required": [
          "id",
          "key",
          "value",
          "changed_at",
          "changed_by"
        ]
      },
      "AppConfigItem": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "value": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "default": {
            "type": "object",
            "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
            "additionalProperties": true
          },
          "type_hint": {
            "type": "string",
            "enum": [
              "number",
              "integer",
              "boolean",
              "string",
              "array",
              "enum"
            ]
          },
          "min": {
            "type": "number"
          },
          "max": {
            "type": "number"
          },
          "enum_values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sensitive": {
            "type": "boolean"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "key",
          "domain",
          "description",
          "value",
          "default",
          "type_hint",
          "sensitive",
          "updated_at"
        ]
      },
      "UpdateAppConfig": {
        "type": "object",
        "properties": {
          "min_app_version_ios": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+$",
            "examples": [
              "1.2.0"
            ]
          },
          "min_app_version_android": {
            "type": "string",
            "pattern": "^\\d+\\.\\d+\\.\\d+$",
            "examples": [
              "1.2.0"
            ]
          },
          "is_maintenance": {
            "type": "boolean"
          },
          "maintenance_message": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "store_url_ios": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "store_url_android": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "walking_cost_yen_per_minute": {
            "type": "integer",
            "minimum": 0,
            "maximum": 500,
            "description": "検索おすすめ順の既定（円/分）。0〜500",
            "examples": [
              20
            ]
          }
        }
      },
      "ClientEventType": {
        "type": "string",
        "enum": [
          "crash",
          "error",
          "performance",
          "ux",
          "lifecycle"
        ]
      },
      "ClientEventSeverity": {
        "type": "string",
        "enum": [
          "fatal",
          "error",
          "warning",
          "info"
        ]
      },
      "ClientEventDevicePlatform": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "ios",
          "android",
          "web"
        ]
      },
      "AdminClientEventListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "event_type": {
            "$ref": "#/components/schemas/ClientEventType"
          },
          "severity": {
            "$ref": "#/components/schemas/ClientEventSeverity"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "app_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "device_platform": {
            "$ref": "#/components/schemas/ClientEventDevicePlatform"
          },
          "os_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ]
          },
          "parking_session_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "event_type",
          "severity",
          "message",
          "app_version",
          "device_platform",
          "os_version",
          "device_model",
          "parking_session_id",
          "created_at"
        ]
      },
      "AdminClientEventDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "event_type": {
            "$ref": "#/components/schemas/ClientEventType"
          },
          "severity": {
            "$ref": "#/components/schemas/ClientEventSeverity"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "stack_trace": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {}
          },
          "app_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "device_platform": {
            "$ref": "#/components/schemas/ClientEventDevicePlatform"
          },
          "os_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ]
          },
          "parking_session_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "examples": [
              "00000000-0000-0000-0000-000000000000"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "event_type",
          "severity",
          "message",
          "stack_trace",
          "metadata",
          "app_version",
          "device_platform",
          "os_version",
          "device_model",
          "parking_session_id",
          "created_at"
        ]
      },
      "AdminI18nMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "lang": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "namespace": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "lang",
          "key",
          "value",
          "namespace",
          "description",
          "created_at",
          "updated_at"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/.well-known/apple-app-site-association": {
      "get": {
        "tags": [
          "well-known"
        ],
        "summary": "iOS Universal Links 設定 / iOS Universal Links Config",
        "description": "Apple が Universal Links 検証時に取得する JSON。\n\n- 認証不要・公開エンドポイント。\n- Content-Type は application/json 固定。\n- Cache-Control: public, max-age=86400（24 時間）。\n- `appIDs` は環境変数 `IOS_APP_ID`（形式: TEAMID.co.jp.parky.app）から取得。\n  または `IOS_APP_TEAM_ID` + `IOS_APP_BUNDLE_ID` を個別に指定（優先）。\n  すべて未設定時は `details` と `webcredentials.apps` を空配列にして返す。\n- 対応パス: /parking-lots/*, /parking-sessions/*, /share/parking/*, /articles/*, /auth/*",
        "responses": {
          "200": {
            "description": "Apple App Site Association",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppleAppSiteAssociation"
                }
              }
            }
          }
        },
        "operationId": "webWellKnownAppleAppSiteAssociationList"
      }
    },
    "/.well-known/assetlinks.json": {
      "get": {
        "tags": [
          "well-known"
        ],
        "summary": "Android App Links 設定 / Android App Links Config",
        "description": "Android が App Links 検証時に取得する JSON。\n\n- 認証不要・公開エンドポイント。\n- Content-Type は application/json 固定。\n- Cache-Control: public, max-age=86400（24 時間）。\n- `package_name` は環境変数 `ANDROID_PACKAGE_NAME` から取得。\n- `sha256_cert_fingerprints` は環境変数 `ANDROID_APP_CERT_FINGERPRINTS`（カンマ区切り）から取得。\n  未設定時は空配列 `[]`（Android 側は App Links 検出しないだけで 200）。",
        "responses": {
          "200": {
            "description": "Digital Asset Links",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetLinksResponse"
                }
              }
            }
          }
        },
        "operationId": "webWellKnownAssetlinksList"
      }
    },
    "/v1/admin/admins": {
      "get": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "管理者一覧 / Admin List",
        "description": "### 用途\n管理者ポータルにアクセスできる **管理者アカウント（ホワイトリスト）** の一覧を返す。\nname / email の部分一致検索、status・role_id でのフィルタ、\nname / email / created_at / last_login_at でのソートに対応する。\n\n### 認証・認可\n`requireAdmin` + `admin:manage`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "role_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "email",
                "created_at",
                "last_login_at"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "required": false,
            "name": "order",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Admin"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          }
        },
        "operationId": "adminAdminsList"
      },
      "post": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "管理者を作成 / Create Admin",
        "description": "### 用途\n新しい管理者アカウントを発行する。Supabase Auth にユーザー作成し、admins を INSERT。\n自動生成した初期パスワードを `initial_password` で返す（画面で 1 度だけ表示）。\n\n### 認証・認可\n`requireAdmin` + `admin:manage` + signup rate limit (index.ts で適用)。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "role_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "password": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 128
                  }
                },
                "required": [
                  "email",
                  "name",
                  "role_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み + 発行した初期 PW",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Admin"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "initial_password": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "initial_password"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdminsCreate"
      }
    },
    "/v1/admin/admins/me": {
      "get": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "ログイン中管理者のプロフィールを返す（ロール検証用）",
        "description": "### 用途\n管理者ポータルのフロントエンドが「sign-in 直後 / セッション復元時」に\n呼び出してロール検証する軽量 endpoint。`requireAdmin` middleware を\n通過した時点で `admins.user_id = JWT.sub` かつ `status='active'` が\n保証される。\n\n### 期待する挙動\n- 認可成功: 200 + 自身の admin プロフィール\n- 非管理者（owners.user_id しか持たない / 一般ユーザー）: 401/403\n  (`unauthenticated` / `admin_not_found`)\n- suspended/disabled: 403 (`admin_status_inactive`)\n\n### Admin Portal 側の使い方\nAuthContext.signIn() 成功直後 + RequireAuth マウント時にこの endpoint\nを呼び、401/403/404 なら supabase.auth.signOut() + LoginPage に\n「管理者権限がありません」表示。",
        "responses": {
          "200": {
            "description": "管理者プロフィール",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminMe"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdminsMe"
      }
    },
    "/v1/admin/admins/me/notification-prefs": {
      "get": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "自分の通知設定を取得 / Get My Notification Settings",
        "description": "### 用途\nログイン中の管理者自身の通知トグル設定（`new_owner` / `new_parking` / `sales_daily` /\n`sales_monthly` / `system_alert` の 5 項目）を返す。未設定キーはサーバー側で `true` 既定値で埋める。\n\n### 管理者ポータルでの使用タイミング\n- 設定ページ（SettingsPage）の通知タブを開いたとき\n- ヘッダーから「通知設定」モーダルを開いたとき\n\n### 認証・認可\n`requireAdmin` 必須。`auth.uid` から admins レコードを参照（自分の行のみ）。\n\n### 挙動・制約\nadmins.notification_prefs（jsonb）から取得し、`DEFAULT_ADMIN_NOTIFICATION_PREFS` とマージして返す。\nDB エラーは `withPgError` 経由で ApiError に変換して throw（silent bypass を避け、フロント側でリトライ/通知できるように）。\n\n### 関連\n- `PUT /v1/admin/admins/me/notification-prefs` — 通知設定を保存",
        "responses": {
          "200": {
            "description": "通知設定 JSON（未設定キーはデフォルト値で埋める）",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminNotificationPrefs"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdminsMeNotificationPrefsList"
      },
      "put": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "自分の通知設定を更新 / Update My Notification Settings",
        "description": "### 用途\nログイン中管理者の通知トグル 5 項目を一括上書きする。部分更新ではなく全体置換。\n\n### 管理者ポータルでの使用タイミング\n- 設定ページの通知タブで「保存」ボタンを押したとき\n- 通知ベル → 設定で個別トグルを切り替えた直後（オートセーブ時）\n\n### 認証・認可\n`requireAdmin` 必須。自分の admins.user_id 行のみ更新。他人の通知設定は変更不可。\n\n### 挙動・制約\nadmins.notification_prefs に jsonb で全フィールドを上書き。\n対応する admins 行が無い場合は 404 `not_found`。成功時に `admin.update_notif_prefs` を\n`admin_activity_logs` にベストエフォート記録（diff として送信ボディを保存）。\n\n### 関連\n- `GET /v1/admin/admins/me/notification-prefs` — 現在値の取得",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminNotificationPrefs"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後の通知設定 JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminNotificationPrefs"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdminsMeNotificationPrefsReplace"
      }
    },
    "/v1/admin/admins/{id}/reset-password": {
      "post": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "管理者パスワードをリセット / Reset Admin Password",
        "description": "### 用途\n対象管理者の Supabase Auth パスワードを新しい 16 文字に強制リセットする。\n\n### 認証・認可\n`requireAdmin` + `admin:manage` + reset-password rate limit。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "発行した PW",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "new_password": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "new_password"
                  ]
                }
              }
            }
          }
        },
        "operationId": "adminAdminsResetPasswordCreate"
      }
    },
    "/v1/admin/admins/{id}": {
      "patch": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "管理者情報を更新 / Update Admin",
        "description": "### 用途\n既存管理者の表示名・割当ロール・ステータスを部分更新する。\n\n### 認証・認可\n`requireAdmin` + `admin:manage`。role_id / status 変更には S-07 権限昇格チェックを適用。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "role_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Admin"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "存在しない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdminsUpdate"
      },
      "delete": {
        "tags": [
          "管理者 / Admins"
        ],
        "summary": "管理者を削除 / Delete Admin",
        "description": "### 用途\n管理者アカウントを完全に削除する。admins 行を物理削除し、紐づく Supabase Auth ユーザーも削除する。\n\n### 認証・認可\n`requireAdmin` + `admin:manage`。\n\n### reason 必須化 (2026-05-11)\n破壊的操作のため body に `reason` (10〜500 文字) が必須。admin_activity_logs.reason に保存され\nAdminActivityPage で参照される。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "破壊的操作 (delete / bulk update / role change 等) の理由。 10〜500 文字。 admin_activity_logs.reason 列に保存され、AdminActivityPage で参照される。",
                    "examples": [
                      "ユーザーから利用規約違反の通報を受けたため"
                    ]
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdminsDelete"
      }
    },
    "/v1/admin/roles": {
      "get": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロール一覧 / Role List",
        "description": "### 用途\nRBAC のロール定義一覧を返す。`is_system=true`（admin / viewer 等の組込ロール）を先頭に固定し、\n続けてカスタムロールを name 昇順で返す。色（color）はバッジ表示用のメタデータ。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面の左ペイン（ロール選択リスト）の初期表示\n- 「管理者管理」追加モーダルのロール選択ドロップダウン\n- 一括ロール変更のオプションリスト構築\n\n### 認証・認可\n`requireAdmin` 必須。RBAC 自体の閲覧は管理者全員に許可。\n\n### 挙動・制約\n並び順は `is_system DESC, name ASC` 固定（システムロール優先）。ページングは `paginated()` で\n全件返却（ロール数は通常 10 件未満を想定）。`role_permissions` は別エンドポイントで取得する。\n\n### 関連\n- `GET /v1/admin/role-permissions` — 全ロール × 権限キーのマトリクス取得\n- `GET /v1/admin/roles/{role_code}/permissions` — 特定ロールの権限キー一覧",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Role"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesList"
      },
      "post": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロールを作成 / Create Role",
        "description": "### 用途\n新しいカスタムロールを 1 件作成する。`is_system=false` 固定で挿入されるため、\nシステムロール（admin / viewer 等）と区別され、後から削除可能になる。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面の「ロールを追加」ボタン押下時\n- 部署横断で権限セットを切り出したいとき（例: 売上閲覧専用ロール）\n\n### 認証・認可\n`requireAdmin` 必須。RBAC 編集権を持つ管理者のみが本来は呼ぶべき（権限チェックは画面側 + role.update_permissions 監査で担保）。\n\n### 挙動・制約\nname / description / color を受け取り `roles` テーブルに 1 行 INSERT。\ncolor はロールバッジの背景色用 HEX（既定 `#8a85a0`）。description は最大 500 文字。\n成功時に `role.create` を `admin_activity_logs` にベストエフォート記録。\n\n### 関連\n- `PUT /v1/admin/roles/{role_code}/permissions` — 作成直後に権限キーを割当\n- `PATCH /v1/admin/roles/{role_code}` — 名称・色を後から変更",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500,
                    "default": ""
                  },
                  "color": {
                    "type": "string",
                    "default": "#8a85a0"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesCreate"
      }
    },
    "/v1/admin/roles/{role_code}": {
      "patch": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロールを更新（natural key = admin.roles.role_code）",
        "description": "### 用途\n既存ロールの name / description / color を部分更新する。`is_system` フラグ自体は変更不可。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面でロール名変更・色変更・説明文編集の保存ボタン押下時\n- ロール命名のリブランディング（例: editor → content_editor）\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n送信ボディが空の場合は現行レコードを 200 で返す（idempotent）。`sql(body)` で動的 SET 句を構築。\n対象が無い場合は 404 `not_found`。成功時に `role.update` を `admin_activity_logs` にベストエフォート記録（diff を metadata 保持）。\n\n### 関連\n- `PUT /v1/admin/roles/{role_code}/permissions` — 権限マトリクスの一括差し替え\n- `DELETE /v1/admin/roles/{role_code}` — ロール削除（is_system=true は不可）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "role_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "color": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesUpdate"
      },
      "delete": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロールを削除 / Delete Role",
        "description": "### 用途\nカスタムロールを物理削除する。組込ロール（`is_system=true`）は WHERE 句で除外されるため\nリクエストが通っても削除されず 204 が返る（fail-safe）。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面でカスタムロールのゴミ箱ボタン押下時\n- 不要になった役割定義のクリーンアップ\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n事前に SELECT して name を取得 → `DELETE ... WHERE id=$1 AND is_system=false`。\n削除後に `role.delete` を `admin_activity_logs` にベストエフォート記録。\n\n### 関連\n- `PATCH /v1/admin/admins/{id}` — 削除前に対象ユーザーのロールを付け替え\n\n### reason 必須化 (2026-05-11)\n破壊的操作のため body に `reason` (10〜500 文字) が必須。admin_activity_logs.reason に保存される。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "role_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "破壊的操作 (delete / bulk update / role change 等) の理由。 10〜500 文字。 admin_activity_logs.reason 列に保存され、AdminActivityPage で参照される。",
                    "examples": [
                      "ユーザーから利用規約違反の通報を受けたため"
                    ]
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesDelete"
      }
    },
    "/v1/admin/roles/{role_code}/permissions": {
      "get": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロール権限一覧（natural key = role_code）",
        "description": "### 用途\n指定ロールに紐づく権限キー（`role_permissions.permission_key`）の配列を返す。\nレスポンスは `[{ permission: 'admin.parking_lot.read' }, ...]` 形式に整形して返す。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面で個別ロールを選択したとき（チェックボックス初期値の取得）\n- 単一ロールの権限プレビュー / コピー元として参照する場合\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`role_permissions` を `role_id = $1` で SELECT してそのまま配列化。並び順は DB の物理順序（保証なし）。\nロールが存在しない場合も空配列 200 を返す（404 にはしない設計）。\n\n### 関連\n- `PUT /v1/admin/roles/{role_code}/permissions` — 権限マトリクスを一括差し替え\n- `GET /v1/admin/role-permissions` — 全ロールまとめて role_id→keys[] マップで取得",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "role_code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "権限キー一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "permission": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "permission"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesPermissionsList"
      },
      "put": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロール権限マトリクスを一括保存（natural key = role_code）",
        "description": "### 用途\n指定ロールの権限キー配列を完全置換する。送信した `permissions` がそのまま新しい正解になり、\nそれ以外の既存キーは削除される。差分計算ではなく **DELETE → INSERT のスナップショット型 API**。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面の **「保存」ボタン押下時のみ**（オートセーブしないポリシー）\n- 権限テンプレートの一括コピー機能\n\n### 認証・認可\n`requireAdmin` 必須。RBAC の核心 API なので `role.update_permissions` の監査ログを必ず残す。\n\n### 挙動・制約\n1 トランザクション内で `DELETE FROM admin.role_permissions WHERE role_id = $1` → 新キーを bulk INSERT。\n送信配列が空ならその役割の全権限を剥奪する（無権限ロールにできる）。\n成功時に `role.update_permissions` を `admin_activity_logs` に記録（permissions と count を metadata 保持）。\n\n### 関連\n- `GET /v1/admin/roles/{role_code}/permissions` — 保存前の現在値取得\n- `GET /v1/admin/role-permissions` — 保存後の全体マップで再描画\n\n### reason 必須化 (2026-05-11)\n破壊的操作 (権限マトリクスの DELETE → INSERT) のため body に `reason` (10〜500 文字) が必須。\nadmin_activity_logs.reason に保存される。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "role_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "破壊的操作 (delete / bulk update / role change 等) の理由。 10〜500 文字。 admin_activity_logs.reason 列に保存され、AdminActivityPage で参照される。",
                    "examples": [
                      "ユーザーから利用規約違反の通報を受けたため"
                    ]
                  }
                },
                "required": [
                  "permissions",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "置き換え後の件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "role_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "role_id",
                    "count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesPermissionsReplace"
      }
    },
    "/v1/admin/roles/by-name/{name}": {
      "get": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロール詳細 / Role Detail",
        "description": "### 用途\nUUID ではなく role 名（`admin.roles.name`、`uq_roles_name` UNIQUE）でロール 1 件を取得する。\n\n### 認証・認可\n`requireAdmin` + `withPermission('role:manage')`。`/{id}` 版と完全同一。\n\n### 関連\n- `GET /v1/admin/roles/{id}` — UUID 版",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesByNameGet"
      },
      "patch": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロールを更新 / Update Role",
        "description": "### 用途\nUUID を経由せず role 名でロールを部分更新する。`/{id}` 版と同じ動作。\n\n### 認証・認可\n`requireAdmin` + `withPermission('role:manage')`。\n\n### 関連\n- `PATCH /v1/admin/roles/{id}` — UUID 版",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "color": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesByNameUpdate"
      },
      "delete": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "ロールを削除 / Delete Role",
        "description": "### 用途\nUUID を経由せず role 名でロールを削除する。\n\n### 認証・認可\n`requireAdmin` + `withPermission('role:manage')`。\n\n### 関連\n- `DELETE /v1/admin/roles/{id}` — UUID 版\n\n### reason 必須化 (2026-05-11)\n破壊的操作のため body に `reason` (10〜500 文字) が必須。admin_activity_logs.reason に保存される。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "破壊的操作 (delete / bulk update / role change 等) の理由。 10〜500 文字。 admin_activity_logs.reason 列に保存され、AdminActivityPage で参照される。",
                    "examples": [
                      "ユーザーから利用規約違反の通報を受けたため"
                    ]
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolesByNameDelete"
      }
    },
    "/v1/admin/role-permissions": {
      "get": {
        "tags": [
          "ロール / Roles"
        ],
        "summary": "全ロール×権限マップ / All Role-Permission Map",
        "description": "### 用途\n全ロールの権限キーをまとめて `{ role_id: ['perm.a', 'perm.b', ...], ... }` のマップで返す。\n管理者ポータルの権限マトリクス UI を 1 リクエストで描画するための集約 API。\n\n### 管理者ポータルでの使用タイミング\n- 「権限管理」画面の初期ロード時（マトリクス全体を一括取得）\n- 権限変更後のリフレッシュ（保存ボタン押下後の再描画）\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 関連\n- `GET /v1/admin/roles` — ロールのメタ情報（name / color / is_system）\n- `PUT /v1/admin/roles/{id}/permissions` — 個別ロールの権限を保存",
        "responses": {
          "200": {
            "description": "role_id → 権限キー配列のマップ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRolePermissionsList"
      }
    },
    "/v1/admin/parking-lots/{id}/owners": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "紐付きオーナー一覧 / Linked Owner List",
        "description": "### 用途\n指定駐車場に紐付く全オーナー（`owners`）を返す。中間テーブル `parking_lot_owners` から\nLEFT JOIN で `owners` 本体（id / name / email / status）を埋めて返す。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場詳細 → 「オーナー」タブを開いた時\n- 売上分配やレポート送付先確認のためのオーナー特定\n- オーナー管理画面から駐車場に逆引きしたい時\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場閲覧権限を持つロールが必要。\n\n### 挙動・制約\n- LEFT JOIN なので `owners` 行がすでに削除済みの場合 `owners: null` で返る\n- 並び順指定なし（中間テーブルの自然順）\n- ページングなし（1 駐車場あたりオーナー数は通常少数を想定）\n\n### 関連\n- `POST /v1/admin/parking-lots/{id}/owners` — オーナー紐付け追加",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "オーナー一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "owner_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "owners": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "email",
                          "status"
                        ]
                      }
                    },
                    "required": [
                      "owner_id",
                      "owners"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsOwnersList"
      },
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "オーナーを紐付け / Link Owner",
        "description": "### 用途\n駐車場と既存オーナー（`owners`）を中間テーブル `parking_lot_owners` に INSERT して紐付ける。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場詳細 → 「オーナー」タブで「オーナー追加」ボタン → オーナー検索 → 確定\n- オーナー管理画面から「この物件にオーナー紐付け」\n- 売上分配の事前設定\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場オーナー編集権限を持つロールが必要。\n\n### 挙動・制約\n- 単純な INSERT。同じ組合せを 2 回入れるとユニーク制約違反 → `translatePgError` で標準エラー化\n- 1 駐車場に複数オーナーを紐付け可能（共有物件想定）\n- 副作用: `admin_activity_logs` に `parking_lot.add_owner` と owner_id を記録\n\n### 関連\n- `GET /v1/admin/parking-lots/{id}/owners` — 紐付き一覧",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  }
                },
                "required": [
                  "owner_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "紐付け成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsOwnersCreate"
      }
    },
    "/v1/admin/parking-lots": {
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場を登録 / Create Parking Lot",
        "description": "### 用途\n駐車場本体と関連子テーブルを 1 リクエスト・1 トランザクションで atomic 登録する。\nPコレからの取り込みや admin ポータルの「新規駐車場登録」ウィザードの最終送信で使う。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場管理 → 「新規追加」ウィザード最終ステップの保存\n- Pコレ一括取り込みバッチから 1 行ずつ呼ぶ（別 endpoint `/import/commit` 内部でも利用可能）\n- 地図クリック → 「ここを登録」簡易フォーム\n\n### 認証・認可\n`requireAdmin` 必須 + RBAC `parking_lot:write` 権限（index.ts の親ルーターで gate 適用済み）。\n\n### 挙動・制約\n- 子配列（`hours` / `pricing_rules` / `images` / `tags` / `owner_ids`）は全て optional。\n- 省略時は対応する子テーブルに INSERT しない（後方互換: 旧 POST と同等の動作）。\n- 空配列を渡しても子 0 件で動作（省略と同義）。\n- `asset_id` / `owner_id` は事前存在必須。FK 違反は `withPgError` が変換。\n- `tag_code` は `tags.slug` を参照するコード値。未知 code は 400 `unknown_tag_code` + `detail.unknown_codes` で拒否。\n- `place_id` 等のユニーク制約違反は `translatePgError` → 標準 API エラー。\n- `parking_lots.location` は lat/lng から BEFORE INSERT trigger で自動同期（archive/014）。\n- 成功時: `admin_activity_logs` に `parking_lot.create_bundle` を best-effort で記録。\n\n### 関連\n- `PATCH /v1/admin/parking-lots/{id}` — 同 shape で部分更新\n- `POST /v1/owner/parking-lots/mine` — オーナーセルフ登録版（owner_ids は server 側で自動紐付け、status=pending 強制）\n- 個別編集用: `POST /{id}/images` / `PUT /{id}/tags` / `PUT /{id}/pricing-rules` / `POST /{id}/owners` — 既存のまま残る",
        "requestBody": {
          "description": "駐車場本体 + 子テーブル（hours / pricing_rules / images / tags / owner_ids）の全量。子配列は全て optional。",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParkingLotBundleCreateBody"
              },
              "example": {
                "parking_lot": {
                  "name": "晴海西口第一駐車場",
                  "address": "東京都中央区晴海20-39",
                  "status": "active",
                  "source": "manual",
                  "total_spaces": 12,
                  "operating_hours": "24h",
                  "structure": "mechanical",
                  "entry_method": "mechanical",
                  "entry_difficulty": "easy",
                  "max_height_m": 1.55,
                  "max_width_m": 1.95,
                  "max_length_m": 5.3,
                  "max_weight_t": 2.5,
                  "min_clearance_cm": 10,
                  "receipt_available": true,
                  "operator_code": "repark",
                  "geometry": {
                    "shape_type": "point",
                    "point": {
                      "lat": 35.669277,
                      "lng": 139.758991
                    }
                  }
                },
                "hours": [
                  {
                    "day_type": "all",
                    "is_24h": true,
                    "is_closed": false
                  }
                ],
                "pricing_rules": [
                  {
                    "rule_order": 1,
                    "category": "unit",
                    "day_type": "all",
                    "time_start": "08:00",
                    "time_end": "22:00",
                    "per_minutes": 30,
                    "price_minor": 200
                  },
                  {
                    "rule_order": 2,
                    "category": "unit",
                    "day_type": "all",
                    "time_start": "22:00",
                    "time_end": "08:00",
                    "per_minutes": 60,
                    "price_minor": 150
                  },
                  {
                    "rule_order": 3,
                    "category": "cap",
                    "day_type": "weekday",
                    "cap_type": "duration",
                    "cap_duration_hours": 24,
                    "cap_price_minor": 1800,
                    "cap_repeat": true
                  }
                ],
                "images": [
                  {
                    "asset_id": "11111111-2222-3333-4444-555555555555",
                    "is_main": true,
                    "sort_order": 1
                  },
                  {
                    "asset_id": "22222222-3333-4444-5555-666666666666",
                    "is_main": false,
                    "sort_order": 2
                  },
                  {
                    "asset_id": "33333333-4444-5555-6666-777777777777",
                    "is_main": false,
                    "sort_order": 3
                  }
                ],
                "tags": [
                  {
                    "tag_code": "covered",
                    "state": true
                  },
                  {
                    "tag_code": "open_24h",
                    "state": true
                  },
                  {
                    "tag_code": "entry_24h",
                    "state": true
                  },
                  {
                    "tag_code": "oversized_ok",
                    "state": false
                  },
                  {
                    "tag_code": "monthly_available",
                    "state": true
                  },
                  {
                    "tag_code": "motorcycle_ok",
                    "state": true
                  },
                  {
                    "tag_code": "wheelchair_accessible",
                    "state": true
                  },
                  {
                    "tag_code": "barrier_free",
                    "state": true
                  },
                  {
                    "tag_code": "ev_charging",
                    "state": false
                  },
                  {
                    "tag_code": "reservable",
                    "state": false
                  },
                  {
                    "tag_code": "has_max_fee",
                    "state": true
                  },
                  {
                    "tag_code": "security_camera",
                    "state": true
                  },
                  {
                    "tag_code": "coin_500_or_less",
                    "state": false
                  },
                  {
                    "tag_code": "near_station",
                    "state": true
                  },
                  {
                    "tag_code": "low_price",
                    "state": false
                  },
                  {
                    "tag_code": "partner_facility",
                    "state": false
                  },
                  {
                    "tag_code": "pay_cash",
                    "state": true
                  },
                  {
                    "tag_code": "pay_bills_only",
                    "state": false
                  },
                  {
                    "tag_code": "pay_credit_card",
                    "state": true
                  },
                  {
                    "tag_code": "pay_e_money",
                    "state": true
                  },
                  {
                    "tag_code": "pay_qr_code",
                    "state": false
                  }
                ],
                "owner_ids": [
                  "99999999-8888-7777-6666-555555555555"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "登録成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingLotBundleResponse"
                },
                "example": {
                  "parking_lot": {
                    "id": "01234567-89ab-cdef-0123-456789abcdef",
                    "name": "晴海西口第一駐車場",
                    "address": "東京都中央区晴海20-39",
                    "geometry": {
                      "shape_type": "point",
                      "point": {
                        "lat": 35.669277,
                        "lng": 139.758991
                      }
                    },
                    "status": "active",
                    "source": "manual",
                    "total_spaces": 12,
                    "created_at": "2026-04-22T10:30:00.000Z",
                    "updated_at": "2026-04-22T10:30:00.000Z"
                  },
                  "hours_count": 1,
                  "pricing_rules_count": 3,
                  "images_count": 3,
                  "tags_count": 21,
                  "owners_count": 1
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsCreate"
      }
    },
    "/v1/admin/parking-lots/{id}": {
      "patch": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場を更新 / Update Parking Lot",
        "description": "### 用途\n駐車場本体と子テーブルをまとめて更新。子配列のセマンティクス:\n- **省略**: その子テーブルは touch しない\n- **空配列**: 全削除（`hours` / `pricing_rules` / `images` / `owner_ids`）または何もしない（`tags`）\n- `hours` / `pricing_rules`: 完全置換（DELETE → INSERT）\n- `images` / `owner_ids`: 差分適用（body に無い既存は DELETE、追加は INSERT、既存は UPDATE）\n- `tags`: 差分適用。`state=null` の行は DELETE（= 不明に戻す）、それ以外は UPSERT\n\n### 管理者ポータルでの使用タイミング\n- 駐車場詳細の編集画面でまとめ保存\n- Pコレ再取込で既存 lot の子テーブルもまるごと最新化したいとき\n\n### 認証・認可\n`requireAdmin` 必須 + RBAC `parking_lot:write` 権限（index.ts の親ルーターで gate 適用済み）。\n\n### 挙動・制約\n- `parking_lot` セクションが空オブジェクトの場合は本体 UPDATE をスキップ\n- 禁則カラム（`id` / `created_at` / `updated_at` / `deleted_at` / `location` / `area`）は schema で弾き、handler でも二重ガード\n- 子も含めて全て `sql.begin()` 内で実行。いずれかの失敗で全体 ROLLBACK\n- 副作用: `admin_activity_logs` に `parking_lot.update_bundle` + 差分件数を記録\n\n### 関連\n- `POST /v1/admin/parking-lots` — 同 shape で新規作成\n- `PATCH /v1/owner/parking-lots/mine/{lotId}` — オーナー版（status 固定・owner_ids 不可）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParkingLotBundleUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingLotBundleResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsUpdate"
      },
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場詳細 (admin 用: status に関わらず取得)",
        "description": "admin 用詳細取得。status='pending' / 'draft' / 'on_hold' / 'withdrawn' / 'rejected' でも返す。公開 endpoint /v1/parking-lots/{id} は active 以外 404 にするため、承認待ち lot を編集する admin は本 endpoint を使う。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "カンマ区切りで追加取得 (images / tags / pricing_rules / operator)",
              "examples": [
                "images,tags,pricing_rules,operator"
              ]
            },
            "required": false,
            "name": "include",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "駐車場詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingLotWithDetail"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsGet"
      }
    },
    "/v1/admin/parking-lots/{id}/images": {
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場に画像を追加 / Add Parking Lot Image",
        "description": "### 用途\n事前にアップロード済みの画像アセット（`assets` テーブル）を駐車場に紐付ける。\n実体ファイルは別 API でアップロード → 返ってきた `asset_id` をここで指定する。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場詳細画面 → 「画像」タブでドラッグ&ドロップ → アセット作成 → このAPIで紐付け\n- ギャラリー編集画面で並び順 (`sort_order`) を指定して追加\n- メイン画像にする場合は `is_main=true` で同時設定\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場画像編集権限を持つロールが必要。\n\n### 挙動・制約\n- `is_main=true` を複数の画像で立てた場合の整合性保証はこの API では行わない（PATCH `/main` を使う）\n- `sort_order` 未指定は NULL 保存、表示順は呼び出し側で並び替え\n- 副作用: `admin_activity_logs` に `parking_lot.add_image` を記録\n\n### 関連\n- `PATCH /v1/admin/parking-lots/{id}/images/{imageId}/main` — メイン画像切替（既存メインを解除）\n- `DELETE /v1/admin/parking-lots/{id}/images/{imageId}` — 画像削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "asset_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "is_main": {
                    "type": "boolean",
                    "default": false
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                },
                "required": [
                  "asset_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "追加した画像",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    },
                    "parking_lot_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    },
                    "asset_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    },
                    "is_main": {
                      "type": "boolean"
                    },
                    "sort_order": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "parking_lot_id",
                    "asset_id",
                    "is_main",
                    "sort_order"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsImagesCreate"
      }
    },
    "/v1/admin/parking-lots/{id}/images/{imageId}/main": {
      "patch": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "画像をメインに設定 / Set Main Image",
        "description": "### 用途\n指定画像を駐車場の唯一のメイン画像（`is_main=true`）に切り替える。\n同じ駐車場の他の画像は自動的に `is_main=false` に落とされる（アトミック）。\n\n### 管理者ポータルでの使用タイミング\n- 画像ギャラリーで星アイコンをクリック\n- 駐車場一覧のサムネイル変更\n- カードビューで表示する代表画像を切り替えたいとき\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場画像編集権限を持つロールが必要。\n\n### 挙動・制約\n- 1 トランザクションで「同 lot 全画像を false に → 対象画像を true に」\n- 対象 `imageId` が当該駐車場に属さない場合は何も更新されないが 200 を返す\n- 冪等: 既にメインの画像を再度指定しても結果は変わらない\n- 副作用: `admin_activity_logs` に `parking_lot.set_main_image` を記録\n\n### 関連\n- `POST /v1/admin/parking-lots/{id}/images` — 画像追加\n- `DELETE /v1/admin/parking-lots/{id}/images/{imageId}` — 画像削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "imageId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "変更成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsImagesMainUpdate"
      }
    },
    "/v1/admin/parking-lots/{id}/images/{imageId}": {
      "delete": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "画像を削除 / Delete Image",
        "description": "### 用途\n駐車場 ↔ 画像の紐付けレコード（`parking_lot_images`）を削除する。\nアセット（`assets`）本体は削除しない（他の駐車場や記事から参照される可能性があるため）。\n\n### 管理者ポータルでの使用タイミング\n- 画像ギャラリーでゴミ箱アイコンをクリック\n- 不適切画像の差し替え時の旧画像取り外し\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場画像編集権限を持つロールが必要。\n\n### 挙動・制約\n- 物理削除（`DELETE`）。ソフトデリートではない（紐付けテーブルは ID 追跡が不要なため）\n- 該当 ID が存在しない / 他駐車場の画像 ID を渡しても 204（冪等）\n- メイン画像を消した場合の代替指定は呼び出し側責任\n- 副作用: `admin_activity_logs` に `parking_lot.delete_image` を記録\n\n### 関連\n- `POST /v1/admin/parking-lots/{id}/images` — 画像追加\n- `PATCH /v1/admin/parking-lots/{id}/images/{imageId}/main` — メイン画像切替",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "imageId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsImagesDelete"
      }
    },
    "/v1/admin/parking-lots/{id}/tags": {
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "タグを付与 / Add Tag",
        "description": "### 用途\n駐車場に 1 件のタグを付与（または既存タグの状態を更新）する。\n\n- `tag_code` は `tags.slug` を参照するコード値。未知 code は 400 `unknown_tag_code`\n- `state` は `true` | `false` のみ受付。「不明」にしたい場合は DELETE を使う\n- `state` 省略時は `true`\n- `(parking_lot_id, tag_id)` の UNIQUE に対し UPSERT",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tag_code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "pattern": "^[a-z][a-z0-9_]*$",
                    "description": "タグを一意に識別するコード値。`tags.slug` を参照する snake_case 文字列。\n現時点でサポートしている値は OAS 例 / `GET /v1/tags` のレスポンスを参照。\n未知の code を送ると 400 `unknown_tag_code` で拒否される。",
                    "examples": [
                      "ev_charging"
                    ]
                  },
                  "state": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "tag_code"
                ]
              },
              "example": {
                "tag_code": "ev_charging",
                "state": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "付与成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsTagsCreate"
      },
      "put": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "タグを一括更新 / Bulk Update Tags",
        "description": "指定駐車場のタグ状態をまとめて更新する。\n\n- body の `tags[].state` は `true` / `false` / `null` の 3値（`null` = 不明）\n- `null` を指定した tag は `DELETE`（= 不明 = 行なし）\n- `true` / `false` は `INSERT ON CONFLICT DO UPDATE SET state = EXCLUDED.state`\n- body に含まれない tag は **一切変更されない**（完全置換ではなく差分適用）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "tag_code": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "pattern": "^[a-z][a-z0-9_]*$",
                          "description": "タグを一意に識別するコード値。`tags.slug` を参照する snake_case 文字列。\n現時点でサポートしている値は OAS 例 / `GET /v1/tags` のレスポンスを参照。\n未知の code を送ると 400 `unknown_tag_code` で拒否される。",
                          "examples": [
                            "ev_charging"
                          ]
                        },
                        "state": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "tag_code",
                        "state"
                      ]
                    },
                    "maxItems": 100
                  }
                },
                "required": [
                  "tags"
                ]
              },
              "example": {
                "tags": [
                  {
                    "tag_code": "covered",
                    "state": true
                  },
                  {
                    "tag_code": "open_24h",
                    "state": true
                  },
                  {
                    "tag_code": "ev_charging",
                    "state": false
                  },
                  {
                    "tag_code": "pay_cash",
                    "state": null
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "applied": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "ok",
                    "applied"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsTagsReplace"
      }
    },
    "/v1/admin/parking-lots/{id}/tags/{tagCode}": {
      "delete": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "タグを解除 / Remove Tag",
        "description": "駐車場からタグを取り外す（= 3値モデルでは「不明」状態に戻す）。\n\n- 物理削除（行が存在しない = `unknown` 扱い）\n- 行が存在しない場合でも 204 を返す（冪等）\n- 未知の `tag_code` は 400 `unknown_tag_code` で拒否（誤字防止）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z][a-z0-9_]*$",
              "description": "削除するタグの `tag_code` (tags.slug)",
              "examples": [
                "ev_charging"
              ]
            },
            "required": true,
            "name": "tagCode",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsTagsDelete"
      }
    },
    "/v1/admin/parking-lots/engagement-stats": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "全駐車場エンゲージメント統計 / All Lots Engagement Stats",
        "description": "### 用途\n全駐車場の累計エンゲージメント指標（保存数 / セッション数 / 検索ヒット数）を\n`parking_lot_id` をキーにしたマップで一括返却する。リスト画面でのバッジ表示や\nヒートマップ用データソースとして利用。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場一覧画面の初期ロードでバッジ（保存◯件 / セッション◯件）を表示\n- マップビューでピンの濃さ・サイズに反映\n- ダッシュボードの人気駐車場ランキング集計の元データ\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場閲覧権限を持つロールが必要。\n\n### 挙動・制約\n- `user_saved_parkings` / `parking_sessions` / `user_activity_log_targets` を UNION ALL で集計\n- ページングなし: 全件を 1 レスポンスで返すため、駐車場数増加時はキャッシュ層を入れる前提\n- `parking_lot_id IS NULL` の行は除外\n- 期間絞り込みなし（累計）。日次推移はダッシュボード API 側で別途提供\n\n### 関連\n- `GET /v1/admin/parking-lots/{id}/engagement-stats` — 単一駐車場の同種統計",
        "responses": {
          "200": {
            "description": "parking_lot_id をキーにしたマップ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "parking_lot_id": {
                        "type": "string"
                      },
                      "saved_count": {
                        "type": "integer"
                      },
                      "session_count": {
                        "type": "integer"
                      },
                      "search_count": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "parking_lot_id",
                      "saved_count",
                      "session_count",
                      "search_count"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsEngagementStatsList"
      }
    },
    "/v1/admin/parking-lots/{id}/engagement-stats": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場エンゲージメント統計 / Lot Engagement Stats",
        "description": "### 用途\n1 駐車場の保存数 / セッション数 / 検索ヒット数の累計を返す。\n詳細画面のサマリーカード表示用。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場詳細画面ヘッダーの統計バッジ表示\n- 「人気度」インジケータの算出元\n- レポート出力時の参照値取得\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場閲覧権限を持つロールが必要。\n\n### 挙動・制約\n- 3 つの COUNT クエリを `Promise.all` で並列実行\n- 累計値（期間絞り込みなし）\n- 該当レコードが 0 件でも 200 で 0 を返す（404 は返さない）\n\n### 関連\n- `GET /v1/admin/parking-lots/engagement-stats` — 全駐車場の一括取得",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "saved_count": {
                      "type": "integer"
                    },
                    "session_count": {
                      "type": "integer"
                    },
                    "search_count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "saved_count",
                    "session_count",
                    "search_count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsEngagementStatsList2"
      }
    },
    "/v1/admin/parking-lots/import/preview": {
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "インポートプレビュー / Import Preview",
        "description": "### 用途\nGoogle スプレッドシート「Pコレ_Master」または直接 CSV を解析し、`parking_lots` への\n取り込み結果を **ドライラン** で集計して返す。DB には一切書き込まない。\nコミット前の差分確認・件数確認用。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場一覧 → 「Pコレ取り込み」モーダルで sheet_url を入力 → 「プレビュー」ボタン\n- `to_create` / `to_update` / `unchanged` / 各種 `skipped` の件数を確認した上で `commit` に進む\n- 未知ヘッダー（`unknown_headers`）の警告表示\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場取り込み権限を持つロールが必要。\n\n### 挙動・制約\n- `sheet_url` または `csv_text` のいずれか必須（両方ある場合は csv_text 優先）\n- Google Sheets は「リンクを知っている人なら閲覧可」設定で `export?format=csv` 経由で取得\n- CSV 内 `place_id` 重複は後勝ちで集約（commit と挙動を揃える）\n- 既存 `place_id` を 1000 件ずつチャンク化して比較し `to_create` / `to_update` / `unchanged` を判定\n- スキップ理由: `disposal`（廃止）/ `no_place_id` / `no_name`\n- 副作用: `admin_activity_logs` に `parking_lot.import_preview` と集計値を記録（DB 行自体は不変）\n\n### 関連\n- `POST /v1/admin/parking-lots/import/commit` — UPSERT 実行（こちらは DB 書き込みあり）",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sheet_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "csv_text": {
                    "type": "string"
                  },
                  "only_verified_active": {
                    "type": "boolean"
                  },
                  "limit": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "プレビュー結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total_rows": {
                      "type": "number"
                    },
                    "unknown_headers": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "to_create": {
                      "type": "number"
                    },
                    "to_update": {
                      "type": "number"
                    },
                    "unchanged": {
                      "type": "number"
                    },
                    "skipped": {
                      "type": "object",
                      "properties": {
                        "disposal": {
                          "type": "number"
                        },
                        "no_place_id": {
                          "type": "number"
                        },
                        "no_name": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "disposal",
                        "no_place_id",
                        "no_name"
                      ]
                    },
                    "sample_create": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "place_id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "address": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "place_id",
                          "name",
                          "address",
                          "status"
                        ]
                      }
                    }
                  },
                  "required": [
                    "total_rows",
                    "unknown_headers",
                    "to_create",
                    "to_update",
                    "unchanged",
                    "skipped",
                    "sample_create"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsImportPreview"
      }
    },
    "/v1/admin/parking-lots/import/commit": {
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "インポート確定 / Commit Import",
        "description": "### 用途\nPコレ_Master CSV を `parking_lots` に **UPSERT 実行** する。`place_id` を一意キーとした\n冪等な取り込み。プレビュー結果に納得してから呼ぶことを想定。\n\n### 管理者ポータルでの使用タイミング\n- プレビュー結果を確認後 → 「コミット」ボタンでこの API を実行\n- 定期的なマスター同期バッチ（管理者手動トリガー）\n- 新エリア追加時の一括登録\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車場取り込み権限を持つロールが必要。\n\n### 挙動・制約\n- `sheet_url` または `csv_text` のいずれか必須\n- 300 行ずつチャンク化して `upsertParkingLotsChunk` で UPSERT。`place_id` 冪等\n- `raw_text` 比較で実質的な変更がない行は `unchanged` としてカウント（無駄な UPDATE を避ける）\n- チャンク単位で例外を捕捉し `errors[]` に格納、処理は継続\n- 副作用: `admin_activity_logs` に `parking_lot.import_commit` と集計値・errors_count を記録\n\n### 関連\n- `POST /v1/admin/parking-lots/import/preview` — ドライラン（必ず先に呼ぶ）",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sheet_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "csv_text": {
                    "type": "string"
                  },
                  "only_verified_active": {
                    "type": "boolean"
                  },
                  "limit": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "取り込み結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total_rows": {
                      "type": "number"
                    },
                    "created": {
                      "type": "number"
                    },
                    "updated": {
                      "type": "number"
                    },
                    "unchanged": {
                      "type": "number"
                    },
                    "skipped": {
                      "type": "number"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "place_id": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "place_id",
                          "message"
                        ]
                      }
                    }
                  },
                  "required": [
                    "total_rows",
                    "created",
                    "updated",
                    "unchanged",
                    "skipped",
                    "errors"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsImportCommitCreate"
      }
    },
    "/v1/admin/parking-lots/{id}/pricing-rules": {
      "put": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "料金ルールを置き換え / Replace Pricing Rules",
        "description": "### 用途\n指定駐車場の **デフォルト料金グループ** 配下の通常料金ルール（`parking_lot_pricing_rules` の `override_id IS NULL`）をトランザクションで全削除 → 一括 INSERT する。\n差分更新ではなく **完全置換** であることに注意。特定日 override 由来の rule は対象外（消さない）。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場詳細画面 → 「料金設定」タブで編集後の保存\n- 料金体系を時間帯別 / 曜日別 / 最大料金などまとめて整理した結果のコミット\n\n### 認証・認可\n`requireAdmin` 必須。`parking_lot:write` 権限を持つロールが必要。\n\n### 挙動・制約\n- 1 トランザクションで `DELETE`（デフォルト group / `override_id IS NULL` のみ）→ `INSERT`\n- admin 編集は権威データとして `source='admin'` / `approved_at=now()` を付与し保存即時に有効化\n- `rule_order` を省略した行はリクエスト配列のインデックスを採用\n- `day_type` 省略時は `all`\n- `category='cap'` または `cap_type` 指定行は最大料金（cap）行として扱う\n- 空配列を渡すと「料金ルールなし」状態になる（DELETE のみ実行）\n- 副作用: `admin_activity_logs` に `parking_lot.update_pricing_rules` と件数を記録\n\n### 関連\n- `PATCH /v1/admin/parking-lots/{id}` — 駐車場本体の更新\n- `POST /v1/admin/parking-lots/{id}/tags` — 決済手段含むタグ付与（057 で payment_methods を tags に統合）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rules": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "rule_order": {
                          "type": "integer"
                        },
                        "category": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 32
                        },
                        "day_type": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "time_start": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "time_end": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "per_minutes": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "price_minor": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "cap_type": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "cap_duration_hours": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "cap_price_minor": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "cap_repeat": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "cap_scope": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "category"
                      ]
                    }
                  }
                },
                "required": [
                  "rules"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "保存された料金ルール件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsPricingRulesReplace"
      }
    },
    "/v1/admin/parking-lots/{lotId}/pricing-groups": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場の料金 group 一覧（admin）",
        "description": "### 用途\n指定 lot の `parking_lot_pricing_groups` を `display_order ASC, code ASC` で返す。\n管理者が料金プラン編集画面で group 一覧を表示する際に呼ぶ。owner 側 endpoint と返却 shape を揃えてある。\n\n### 認証・認可\n`requireAdmin` 必須。GET は親ルーター (parking-lots/index.ts) の RBAC 判定で素通し。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lotId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "料金 group 一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminParkingLotPricingGroup"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsPricingGroupsList"
      },
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "料金 group を新規作成（admin）",
        "description": "### 用途\nlot に新しい pricing group を追加する。`code` は lot 内一意 (citext)。\n`is_default=true` を指定したとき、既存の default group が居れば DB の partial UNIQUE\nで拒否されるので運用上は別 endpoint で先に既存 default を解除してから作成する。\n\n### 認証・認可\n`requireAdmin` + 親ルーターの RBAC `parking_lot:write` (POST) で write 権限を要求。\n\n### 副作用\n- `admin_activity_logs` に `parking_lot.add_pricing_group` を記録。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lotId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminPricingGroupCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "作成された料金 group",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingLotPricingGroup"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsPricingGroupsCreate"
      }
    },
    "/v1/admin/parking-lots/{lotId}/pricing-groups/{id}": {
      "patch": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "料金 group を部分更新（admin）",
        "description": "### 用途\n`code` / `name` / `is_default` / `display_order` を任意項目で部分更新する。\n`code` 変更時は同 lot 内に同名 (citext) が居ないか事前チェック → 衝突なら 400。\n\n### 認証・認可\n`requireAdmin` + 親ルーターの RBAC `parking_lot:write` (PATCH)。\n\n### 副作用\n- `admin_activity_logs` に `parking_lot.update_pricing_group` を記録。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lotId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminPricingGroupUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後の料金 group",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingLotPricingGroup"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsPricingGroupsUpdate"
      },
      "delete": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "料金 group を削除（admin）",
        "description": "### 用途\n物理削除。参照中の `pricing_rules` は FK CASCADE で連動削除、`spots` は FK RESTRICT のため\n参照 spot が残っていると 409 (foreign_key_violation) になる。default group を消す前に\n別 group を default=true へ昇格させること。\n\n### 認証・認可\n`requireAdmin` + 親ルーターの RBAC `parking_lot:delete` (DELETE)。\n\n### 副作用\n- `admin_activity_logs` に `parking_lot.delete_pricing_group` を記録。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lotId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除完了"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsPricingGroupsDelete"
      }
    },
    "/v1/admin/parking-lots/{id}/date-overrides": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "日付オーバーライド一覧（特定日の営業時間・料金）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ParkingLotDateOverride"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsDateOverridesList"
      },
      "post": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "日付オーバーライドを登録 / 上書き (UPSERT)",
        "description": "`{parking_lot_id, override_date}` を一意キーとして UPSERT する。\n既存があれば全フィールド置換、無ければ新規作成。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DateOverrideUpsertBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "登録 / 更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingLotDateOverride"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsDateOverridesCreate"
      }
    },
    "/v1/admin/parking-lots/{id}/date-overrides/{ref}": {
      "patch": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "日付オーバーライドを部分更新（date または uuid で対象指定）",
        "description": "`{ref}` は対象日 (`YYYY-MM-DD`) または override の uuid。形式で自動分岐する。\nuuid 版は bff-client が使うパス。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "ref",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DateOverridePatchBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingLotDateOverride"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsDateOverridesUpdate"
      },
      "delete": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "日付オーバーライドを削除（date または uuid で対象指定）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "ref",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "削除成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsDateOverridesDelete"
      }
    },
    "/v1/admin/parking-lots/{id}/hours": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場の営業時間一覧（window_type 毎に 0 件以上）",
        "description": "parking_lot_hours テーブルの行を配列で返す。\n`?window_type=` 指定時はその window_type だけを、省略時は全 window_type を返す。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "business",
                "entry",
                "exit",
                "after_hours_exit"
              ],
              "description": "時間窓の種別（codes.category_id=`parking_hours_window_type`）。\n- `business`         : 通常営業時間（既存行はすべてこれ扱い。DEFAULT）\n- `entry`            : 入庫可能時間（夜間入庫不可施設など）\n- `exit`             : 出庫可能時間\n- `after_hours_exit` : 営業時間外の出庫可否フラグ（曜日単位）",
              "examples": [
                "business"
              ]
            },
            "required": false,
            "name": "window_type",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（window_type 毎に 0 件以上）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ParkingLotHour"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsHoursList"
      },
      "put": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場の営業時間を完全置換（window_type 単位 or 全置換）",
        "description": "指定 `window_type` に該当する既存行を DELETE → body の `hours` を INSERT で完全置換する。\n`window_type` 省略時は全 window_type を一括置換する。\nbody.hours の各要素の window_type が body.window_type と不一致なら 400。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParkingLotHoursReplaceBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "置換後の行一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ParkingLotHour"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsHoursReplace"
      }
    },
    "/v1/admin/parking-lots/{id}/ingest-payloads": {
      "get": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "この駐車場に紐付く取り込み payload 履歴",
        "description": "ingest pipeline (Phase A OSM / Phase B Google など) の自動マッチ or Landing Zone から admin が手動 link した payload の履歴を返す。新しい順 (captured_at DESC)。superseded / soft-delete 行は除外。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "取り込み payload 履歴",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingLotIngestPayloadsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsIngestPayloadsList"
      }
    },
    "/v1/admin/parking-lots/{id}/status": {
      "patch": {
        "tags": [
          "駐車場 / Parking Lots"
        ],
        "summary": "駐車場ステータスを変更 (admin 汎用)",
        "description": "draft / pending / active / on_hold / withdrawn / rejected の任意遷移。DB トリガの遷移制限は app.bff_admin_override GUC でバイパスする。pending→active 時は配下の未承認 field_values も同時 approve する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingLotStatusChangeBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ステータス変更成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingLotStatusChangeResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingLotsStatusUpdate"
      }
    },
    "/v1/admin/parking-spots": {
      "get": {
        "tags": [
          "parking-spots"
        ],
        "summary": "車室一覧 (admin、lot_id 必須)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminParkingSpot"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSpotsList"
      },
      "post": {
        "tags": [
          "parking-spots"
        ],
        "summary": "車室を新規作成 (admin)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingSpotCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingSpot"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSpotsCreate"
      }
    },
    "/v1/admin/parking-spots/{id}": {
      "get": {
        "tags": [
          "parking-spots"
        ],
        "summary": "車室 1 件取得 (admin)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "1 件",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingSpot"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSpotsGet"
      },
      "patch": {
        "tags": [
          "parking-spots"
        ],
        "summary": "車室を部分更新 (admin)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingSpotUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingSpot"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSpotsUpdate"
      },
      "delete": {
        "tags": [
          "parking-spots"
        ],
        "summary": "車室を soft delete (admin)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除完了"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSpotsDelete"
      }
    },
    "/v1/admin/parking-spots/{id}/restore": {
      "post": {
        "tags": [
          "parking-spots"
        ],
        "summary": "車室の soft delete を解除 (admin 限定)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "復元後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingSpot"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSpotsRestore"
      }
    },
    "/v1/admin/users": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ユーザー一覧 / User List",
        "description": "### 用途\nParky モバイルアプリのエンドユーザー（`app_users` テーブル）一覧を返す。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "email",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "vehicle_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no"
              ]
            },
            "required": false,
            "name": "premium",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminAppUser"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersList"
      }
    },
    "/v1/admin/users/count": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ユーザー総数 / User Count",
        "description": "`app_users` テーブルの全件数（フィルタ無し）を返す。",
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersCount"
      }
    },
    "/v1/admin/users/options": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "軽量ユーザー一覧 / Lightweight User List",
        "description": "ドロップダウン / オートコンプリート選択肢用に id / display_name / email のみ軽量で返す。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "軽量一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "display_name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "display_name",
                          "email"
                        ]
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersOptionsList"
      }
    },
    "/v1/admin/users/{id}": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ユーザー詳細 / User Detail",
        "description": "指定 ID のエンドユーザー 1 件を取得する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAppUser"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersGet"
      }
    },
    "/v1/admin/users/bulk-status": {
      "patch": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ステータスを一括変更 / Bulk Update Status",
        "description": "### 用途\n指定した複数ユーザーの `status` カラムを一括更新する（例: 一斉停止 / 一斉再開）。\n1 リクエストで N 件処理することで往復回数を削減する。\n\n### 管理者ポータルでの使用タイミング\n- 「ユーザー一覧」のチェックボックス選択 → 「停止」「再開」ボタン押下時\n- 通報集中対応で複数アカウントを一括 suspend するとき\n- バッチ的なクリーンアップ運用\n\n### 認証・認可\n`requireAdmin` 必須。**ステータス遷移の妥当性チェックはサーバー側で行わない**ため、\nコードマスター（user_status カテゴリ）に存在する値を渡すのはクライアント責務。\n\n### 挙動・制約\n`UPDATE app_users SET status = $1 WHERE id = ANY($2::uuid[]) RETURNING ...`。\nADMIN-M-06 (admin portal API audit 2026-04-23): 旧仕様は `{updated: count}` のみ返却で、\nFE が変更後の行を欲しい場合は ids 件数だけ個別 GET していた (N+1)。新仕様では `RETURNING`\nで AppUserSchema 列を返却し、FE 側の再取得を不要にする。空配列なら DB 不打鍵で空結果。\n成功時は `app_user.update_status` を `admin_activity_logs` に記録 (ids / status / count を metadata 保持)。\n\n### reason 必須化 (2026-05-11)\n破壊的操作 (bulk update) のため body に `reason` (10〜500 文字) が必須。\nadmin_activity_logs.reason に保存される。\n\n### 関連\n- `GET /v1/admin/users/{id}` — 個別の現状ステータス確認",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "status": {
                    "type": "string"
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "破壊的操作 (delete / bulk update / role change 等) の理由。 10〜500 文字。 admin_activity_logs.reason 列に保存され、AdminActivityPage で参照される。",
                    "examples": [
                      "ユーザーから利用規約違反の通報を受けたため"
                    ]
                  }
                },
                "required": [
                  "ids",
                  "status",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数 + 更新後の行 (AppUserSchema 列のみ明示)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminAppUser"
                      }
                    }
                  },
                  "required": [
                    "updated",
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersBulkStatusUpdate"
      }
    },
    "/v1/admin/users/{id}/saved-parking-lots": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ユーザーの保存駐車場 / User Saved Lots",
        "description": "### 用途\n対象ユーザーが「お気に入り保存」している駐車場 (`user_saved_parkings`) を、\n実際の駐車場メタ情報 (`parking_lots.name` / `address`) と LEFT JOIN して返す。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細画面（UserDetailPage）の「保存」タブを開いたとき\n- サポート対応でよく使う駐車場を確認するとき\n- 駐車場運用の人気チェック\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n並び順は `usp.saved_at DESC`（新しく保存した順）。削除済み駐車場は `parking_lot=null` で返る（ユーザー側の保存履歴は残す）。\nFE 互換性のため `parking_lot`（単数形）と `parking_lots`（複数形のエイリアス）を両方返している。新規実装は単数形を使うこと。\n\n### 関連\n- `GET /v1/admin/users/{id}` — ユーザー本体\n- `GET /v1/admin/users/{id}/activity-counts` — 保存数のカウント",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "user_id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "parking_lot_id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "saved_at": {
                            "type": "string"
                          },
                          "parking_lot": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "examples": [
                                  "00000000-0000-0000-0000-000000000000"
                                ]
                              },
                              "name": {
                                "type": "string"
                              },
                              "address": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "address"
                            ]
                          },
                          "parking_lots": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "examples": [
                                  "00000000-0000-0000-0000-000000000000"
                                ]
                              },
                              "name": {
                                "type": "string"
                              },
                              "address": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "address"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "user_id",
                          "parking_lot_id",
                          "saved_at",
                          "parking_lot"
                        ]
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersSavedParkingLotsList"
      }
    },
    "/v1/admin/users/{id}/exp-progress": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "EXP進捗 / EXP Progress",
        "description": "### 用途\nユーザーのゲーミフィケーション指標を画面側で組み立てるための **生データ束** を返す:\n現在の `user_exp`（total_exp 等）と `level_definitions` 全件（lv ↔ 必要 EXP マップ）。\nクライアント側でレベル計算 / 次レベルまでの残 EXP / 進捗バーを算出する。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細画面の「ゲーミフィケーション」タブ初回表示\n- サポート対応で「現在 LV / 累計 EXP」を確認するとき\n- EXP 補正対応の事前確認\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`user_exp` 行が無い（=活動履歴なし）ユーザーは `user_exp: null` で返る。\n`levels` は `level ASC` で全件返却し、クライアント側でバイナリサーチや累積計算に使う。\nサーバー側でレベル算出はしない（テーブル定義変更時の影響をサーバー側に閉じ込めないため）。\n\n### 関連\n- `GET /v1/admin/users/{id}/activity-counts` — 行動別カウント（活動内訳）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "生データ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_exp": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "user_id": {
                          "type": "string",
                          "format": "uuid",
                          "examples": [
                            "00000000-0000-0000-0000-000000000000"
                          ]
                        },
                        "total_exp": {
                          "type": "integer"
                        },
                        "updated_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "user_id",
                        "total_exp",
                        "updated_at"
                      ]
                    },
                    "levels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "level": {
                            "type": "integer"
                          },
                          "required_exp": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "level",
                          "required_exp"
                        ]
                      }
                    }
                  },
                  "required": [
                    "user_exp",
                    "levels"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersExpProgressList"
      }
    },
    "/v1/admin/users/{id}/activity-counts": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "活動カウント / Activity Counts",
        "description": "### 用途\n指定ユーザーの行動集計 (`user_activity_counts`) を **activity_type 単位の配列** で返す。\n駐車セッション回数・レビュー投稿数・検索回数など、ゲーミフィケーション進捗や\nユーザー分析の基礎データ。1 ユーザーあたり (user_id, activity_type) の UNIQUE で\n複数行存在する。集計行が 1 件も無いユーザーは空配列。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細画面の「活動サマリ」タブを開いたとき\n- ユーザー調査 / サポート対応で行動実績を確認するとき\n\n### 認証・認可\n要 `requireAdmin`。RBAC の `users:read` 権限が必要。\n\n### 挙動・制約\n- `user_activity_counts` から user_id でフィルタした全行を返す\n- 存在しない場合は `[]`（404 ではなく 200 + 空配列）\n- 集計は `user_activity_logs` → カウンターへの upsert で非同期更新されるため、\n  直近行動は数秒〜分オーダで反映される\n\n### 関連\n- `GET /v1/admin/users/{id}` — ユーザー詳細\n- `GET /v1/admin/users/{id}/exp` — EXP 進捗\n- `GET /v1/admin/activity-logs` — 生ログ一覧",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "user_activity_counts 行の配列（activity_type 単位）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UserActivityCountRow"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersActivityCountsList"
      }
    },
    "/v1/admin/users/{id}/badges": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ユーザーの獲得バッジ一覧",
        "description": "### 用途\n対象ユーザーが獲得済みの `user_badges` を `badge_definitions` の主要列付きで返す。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細画面の「ゲーミフィケーション」タブの「獲得バッジ」セクション\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 並び順: `earned_at DESC`\n- 削除済みバッジ定義は `badge=null` で返る（履歴は残す）\n- 該当無しでも 200 + 空配列\n\n### 関連\n- `GET /v1/admin/users/{id}/badge-progress` — 未獲得バッジの進捗\n- `GET /v1/admin/badges/{id}/progress-users` — バッジ単位の取得者一覧",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "獲得バッジ配列",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "examples": [
                          "00000000-0000-0000-0000-000000000000"
                        ]
                      },
                      "user_id": {
                        "type": "string",
                        "format": "uuid",
                        "examples": [
                          "00000000-0000-0000-0000-000000000000"
                        ]
                      },
                      "badge_id": {
                        "type": "string",
                        "format": "uuid",
                        "examples": [
                          "00000000-0000-0000-0000-000000000000"
                        ]
                      },
                      "earned_at": {
                        "type": "string"
                      },
                      "badge": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "icon": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "description",
                          "icon",
                          "category"
                        ]
                      }
                    },
                    "required": [
                      "id",
                      "user_id",
                      "badge_id",
                      "earned_at",
                      "badge"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersBadgesList"
      }
    },
    "/v1/admin/users/{id}/badge-progress": {
      "get": {
        "tags": [
          "ユーザー / Users"
        ],
        "summary": "ユーザーの未獲得バッジ進捗一覧",
        "description": "### 用途\n未獲得かつ is_active=true のバッジについて、対象ユーザーの `user_badge_progress.count` を\n0 補完で全件返す。新規ユーザーで progress 行が無い場合も全アクティブバッジ分の `count: 0` を返す。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細画面の「ゲーミフィケーション」タブの「未獲得バッジ進捗」セクション\n- バッジ閾値検証や個別補正の事前確認\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 既獲得バッジは結果に含まれない（`/badges` で取得すること）\n- `is_active=false` のバッジは含まれない\n- 並び順: `bd.sort_order ASC, bd.created_at ASC`（バッジ定義側の表示順に従う）\n\n### 関連\n- `GET /v1/admin/users/{id}/badges` — 既獲得バッジ\n- `POST /v1/admin/badges/{id}/backfill` — 進捗を再計算",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "未獲得バッジ進捗配列",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "badge_id": {
                        "type": "string",
                        "format": "uuid",
                        "examples": [
                          "00000000-0000-0000-0000-000000000000"
                        ]
                      },
                      "count": {
                        "type": "integer"
                      },
                      "badge": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "description": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "icon": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "category": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "threshold": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "is_active": {
                            "type": [
                              "boolean",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "description",
                          "icon",
                          "category",
                          "threshold",
                          "is_active"
                        ]
                      }
                    },
                    "required": [
                      "badge_id",
                      "count",
                      "badge"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUsersBadgeProgressList"
      }
    },
    "/v1/admin/tags": {
      "get": {
        "tags": [
          "タグ / Tags"
        ],
        "summary": "タグ一覧 / Tag List",
        "description": "### 用途\n駐車場に付与可能なタグマスター（`tags`）の一覧を返す。各タグの利用件数\n（`parking_lot_tags` での参照数 = `usage_count`）も相関サブクエリで合算して返す。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場管理 → 「タグ管理」画面の初期ロード\n- 駐車場詳細のタグ付与モーダルでの選択肢ロード（権限分離する場合）\n- カテゴリ別タグ整理時の現状把握\n\n### 認証・認可\n`requireAdmin` 必須。RBAC でタグ閲覧権限を持つロールが必要。\n\n### 挙動・制約\n- 並び順: `sort_order ASC, name ASC`\n- ページング: `PageQuerySchema` 経由（`page` / `limit`）\n- `usage_count` は相関サブクエリで都度算出。タグ数が増えた場合は要キャッシュ検討\n- ソフトデリート概念なし（タグは物理削除で `parking_lot_tags` も CASCADE）\n\n### 関連\n- `POST /v1/admin/tags` — タグ作成\n- `PATCH /v1/admin/tags/{id}` — タグ更新\n- `DELETE /v1/admin/tags/{id}` — タグ削除（CASCADE）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminTag"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTagsList"
      },
      "post": {
        "tags": [
          "タグ / Tags"
        ],
        "summary": "タグを作成 / Create Tag",
        "description": "### 用途\n駐車場に付与するタグを新規作成する。表示名・色・並び順・slug・カテゴリを指定する。\n\n### 管理者ポータルでの使用タイミング\n- タグ管理画面の「新規追加」ボタン → モーダル保存\n- 新カテゴリ運用開始時の一括登録（屋根 / 防犯カメラ / EV充電 など）\n- マイグレーション 028（属性 → タグ統合）後の追加タグ整備\n\n### 認証・認可\n`requireAdmin` 必須。RBAC でタグ編集権限を持つロールが必要。\n\n### 挙動・制約\n- `name` は 1〜50 文字必須\n- `color` 既定値 `#7c5cfc`（Parky パープル）\n- `sort_order` 既定値 0\n- `slug` は英小文字 / 数字 / `_` のみ（`/^[a-z0-9_]+$/`）。1〜50 文字。プログラム参照キーとして使用\n- `category` は任意（カテゴリでタグをグルーピング表示する用）\n- 副作用: `admin_activity_logs` に `tag.create` と name を記録\n\n### 関連\n- `PATCH /v1/admin/tags/{tag_code}` — タグ更新\n- `POST /v1/admin/parking-lots/{id}/tags` — 駐車場へのタグ付与",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "color": {
                    "type": "string",
                    "default": "#7c5cfc"
                  },
                  "sort_order": {
                    "type": "integer",
                    "default": 0
                  },
                  "slug": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "icon_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 64
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminTag"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTagsCreate"
      }
    },
    "/v1/admin/tags/{tag_code}": {
      "patch": {
        "tags": [
          "タグ / Tags"
        ],
        "summary": "タグを更新 / Update Tag",
        "description": "### 用途\n既存タグの表示名・色・並び順・slug・カテゴリを部分更新する。\n\n### 管理者ポータルでの使用タイミング\n- タグ管理画面で編集 → 保存\n- 並び順のドラッグ&ドロップ後コミット\n- カテゴリ再整理（マスターのリネーム・色変更）\n\n### 認証・認可\n`requireAdmin` 必須。RBAC でタグ編集権限を持つロールが必要。\n\n### 挙動・制約\n- path param `{tag_code}` は `tags.slug` (natural key, UNIQUE) を指定\n- body が空オブジェクトの場合は現在値を取得して返す（差分なし保存対応）\n- 該当 tag_code が存在しない場合は 404 `not_found`\n- `slug` 更新時もフォーマット制約（`/^[a-z0-9_]+$/`、1〜50 文字）を適用\n- `UPDATE tags SET ${sql(body)}` の動的展開、未指定カラムは現行値を維持\n- 副作用: `admin_activity_logs` に `tag.update` と diff を記録\n\n### 関連\n- `POST /v1/admin/tags` — 新規作成\n- `DELETE /v1/admin/tags/{tag_code}` — 削除（CASCADE）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "tag_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "color": {
                    "type": "string"
                  },
                  "sort_order": {
                    "type": "integer"
                  },
                  "slug": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "icon_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminTag"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTagsUpdate"
      },
      "delete": {
        "tags": [
          "タグ / Tags"
        ],
        "summary": "タグを削除 / Delete Tag",
        "description": "### 用途\nタグマスターを物理削除する。`parking_lot_tags` の関連行は CASCADE で同時削除される\n（その駐車場に対するタグ状態は「不明」相当に戻る）。\n\n### 管理者ポータルでの使用タイミング\n- タグ管理画面でゴミ箱アイコン → 確認ダイアログ → 削除\n- 廃止カテゴリの整理\n- 重複登録タグの統合後の不要分削除\n\n### 認証・認可\n`requireAdmin` 必須。RBAC でタグ削除権限を持つロールが必要。\n\n### 挙動・制約\n- path param `{tag_code}` は `tags.slug` (natural key, UNIQUE) を指定\n- 物理削除（ソフトデリートではない）。CASCADE による副作用範囲が大きいため UI 側で警告必須\n- 該当 tag_code が存在しない場合でも 204（冪等）\n- 削除前に `name` を SELECT し、`admin_activity_logs.targetLabel` に保持して監査ログを残す\n- 副作用: `admin_activity_logs` に `tag.delete` を記録\n\n### 関連\n- `GET /v1/admin/tags` — 一覧（usage_count を見て影響範囲を確認）\n- `DELETE /v1/admin/parking-lots/{id}/tags/{tagId}` — 個別駐車場の解除（マスターは残す）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "tag_code",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTagsDelete"
      }
    },
    "/v1/admin/operators": {
      "get": {
        "tags": [
          "運営会社 / Operators"
        ],
        "summary": "運営会社一覧 / Operator List",
        "description": "### 用途\n駐車場の **運営会社マスタ** を一覧で返す（旧 operators テーブル相当）。\n実体は `codes(category_id='operator', lang='ja')` に統合済みで、本 API はそれを旧 operators 形状\n（`id` / `slug` / `name` / `color` / `sort_order` / `usage_count`）にアダプタして返す。\n\n### 管理者ポータルでの使用タイミング\n- 「オペレーター管理」画面の一覧表示・ページング送り\n- 駐車場の登録/編集モーダルで「運営会社」セレクトを描画する直前\n\n### 認証・認可\n`requireAdmin` 必須。codes テーブルへの直接 SELECT。\n\n### 関連\n- `POST /v1/admin/operators` — 運営会社を新規追加\n- `PATCH /v1/admin/operators/{slug}` — 名称・色・slug 等を更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOperator"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOperatorsList"
      },
      "post": {
        "tags": [
          "運営会社 / Operators"
        ],
        "summary": "運営会社を作成 / Create Operator",
        "description": "### 用途\n新しい運営会社（駐車場オペレーター）を 1 件追加する。実体は `codes(category_id='operator')` への INSERT。\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 関連\n- `PATCH /v1/admin/operators/{slug}` — 後から名称・slug・色を変更",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "color": {
                    "type": "string",
                    "default": "#7c5cfc"
                  },
                  "sort_order": {
                    "type": "integer",
                    "default": 0
                  }
                },
                "required": [
                  "name",
                  "slug"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOperator"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOperatorsCreate"
      }
    },
    "/v1/admin/operators/{slug}": {
      "patch": {
        "tags": [
          "運営会社 / Operators"
        ],
        "summary": "運営会社を更新 / Update Operator",
        "description": "### 用途\n運営会社レコードを部分更新する。path param の `{slug}` は codes.code（natural key, text）を指定する。\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 関連\n- `DELETE /v1/admin/operators/{slug}` — ソフトデリート",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "color": {
                    "type": "string"
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOperator"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOperatorsUpdate"
      },
      "delete": {
        "tags": [
          "運営会社 / Operators"
        ],
        "summary": "運営会社を削除 / Delete Operator",
        "description": "### 用途\n運営会社をソフトデリートする。`codes.is_deleted=true` を立て、\n紐付いていた `parking_lots.operator_code` を NULL に落として孤立させる。\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 関連\n- `PATCH /v1/admin/operators/{slug}` — 一時非表示にしたいだけなら sort_order や運用メタで制御も検討",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOperatorsDelete"
      }
    },
    "/v1/admin/parking-operators": {
      "get": {
        "tags": [
          "parking-operators"
        ],
        "summary": "運営会社マスタ一覧 (parking_operators 直接)",
        "description": "Phase 0.5 で追加された `parking_operators` テーブルを page で返す。\nlot_count は parking_lots_master 経由で集計した駐車場件数。\ndeleted_at IS NULL のみ返す (include_deleted=true でゴミ箱を含む)。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "運営会社一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ParkingOperator"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingOperatorsList"
      },
      "post": {
        "tags": [
          "parking-operators"
        ],
        "summary": "運営会社を新規作成",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParkingOperatorCreateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingOperator"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingOperatorsCreate"
      }
    },
    "/v1/admin/parking-operators/{id}": {
      "patch": {
        "tags": [
          "parking-operators"
        ],
        "summary": "運営会社を部分更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParkingOperatorUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingOperator"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingOperatorsUpdate"
      },
      "delete": {
        "tags": [
          "parking-operators"
        ],
        "summary": "運営会社をソフトデリート (deleted_at + is_active=false)",
        "description": "parking_lots_master.operator_id は外部キー制約なしで残るが、parking_lots view の operator_code は po.deleted_at IS NULL JOIN で自動的に NULL になる。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingOperatorsDelete"
      },
      "get": {
        "tags": [
          "parking-operators"
        ],
        "summary": "運営会社を 1 件取得",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "operator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkingOperator"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingOperatorsGet"
      }
    },
    "/v1/admin/notifications": {
      "get": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "管理者通知一覧 / Admin Notification List",
        "description": "### 用途\n管理者向け通知（`admin_notifications`）の一覧を返す。カテゴリ・既読状態でフィルタ可能。\n\n### 管理者ポータルでの使用タイミング\n- ヘッダーの通知ベルアイコンから開くドロワー初期表示\n- 「管理者通知」画面のメイン一覧\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `deleted_at IS NULL` のみ返却（削除済みは `/deleted` 側）\n- `created_at DESC` で降順\n\n### 関連\n- `GET /v1/admin/notifications/deleted` — ゴミ箱\n- `POST /v1/admin/notifications/{id}/read` — 既読化",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "read",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminNotification"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsList"
      },
      "post": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "管理者通知を作成 / Create Admin Notification",
        "description": "### 用途\n管理者から他の管理者へのメッセージ通知を `admin_notifications` に作成する。\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 関連\n- `POST /v1/admin/notifications/{id}/read` — 既読化\n- `POST /v1/admin/notifications/read-all` — 全件既読",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "category": {
                    "type": "string",
                    "default": "システム"
                  },
                  "avatar": {
                    "type": "string",
                    "default": "P"
                  }
                },
                "required": [
                  "title",
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminNotification"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsCreate"
      }
    },
    "/v1/admin/notifications/deleted": {
      "get": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "削除済み一覧 / Trash",
        "description": "### 用途\nソフトデリート済み（`deleted_at IS NOT NULL`）の管理者通知一覧を返す。\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 関連\n- `POST /v1/admin/notifications/{id}/restore` — 復元\n- `DELETE /v1/admin/notifications/{id}/purge` — 完全削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "read",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminNotification"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsDeletedList"
      }
    },
    "/v1/admin/notifications/{id}/read": {
      "post": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "既読にする / Mark as Read",
        "description": "### 用途\n管理者通知 1 件を既読（`read = true`）にする。\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 関連\n- `POST /v1/admin/notifications/read-all` — 全件既読",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsRead"
      }
    },
    "/v1/admin/notifications/read-all": {
      "post": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "すべて既読にする / Mark All as Read",
        "description": "### 用途\n未読の管理者通知を一括で既読化する。\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 関連\n- `POST /v1/admin/notifications/{id}/read` — 個別既読",
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsReadAllCreate"
      }
    },
    "/v1/admin/notifications/{id}": {
      "delete": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "ソフト削除 / Soft Delete",
        "description": "### 用途\n管理者通知を論理削除（`deleted_at = NOW()`）してゴミ箱へ移動する。\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 関連\n- `POST /v1/admin/notifications/{id}/restore` — 復元\n- `DELETE /v1/admin/notifications/{id}/purge` — 完全削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsDelete"
      }
    },
    "/v1/admin/notifications/{id}/restore": {
      "post": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "復元 / Restore",
        "description": "### 用途\nゴミ箱の管理者通知を `deleted_at = NULL` に戻して通常一覧へ復帰させる。\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 関連\n- `DELETE /v1/admin/notifications/{id}` — ソフト削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsRestore"
      }
    },
    "/v1/admin/notifications/{id}/purge": {
      "delete": {
        "tags": [
          "通知 / Notifications"
        ],
        "summary": "完全削除 / Purge",
        "description": "### 用途\n管理者通知を物理削除（purge）する。ゴミ箱画面から「元に戻せない」完全削除。\n\n### 認証・認可\n要 `requireAdmin`。\n\n### 関連\n- `DELETE /v1/admin/notifications/{id}` — ソフト削除（ゴミ箱行き）\n- `POST /v1/admin/notifications/{id}/restore` — ソフト削除から復元",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminNotificationsPurgeDelete"
      }
    },
    "/v1/admin/user-notifications": {
      "get": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "ユーザー向け通知一覧 / User Notification List",
        "description": "### 用途\nユーザー向けプッシュ通知（`user_notifications`）の管理一覧。下書き・予約・送信中・\n送信済みのレコードを横断的に表示する。タイトル / 本文 / 通知タイプ / ステータスで絞り込み可能。\n\n### 管理者ポータルでの使用タイミング\n- 「通知メッセージ管理」画面の初期表示\n- ステータス・タイプフィルタ操作時の再取得\n- タイトル / 本文の文字列検索\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 既定では `deleted_at IS NULL`。`deleted=true` でゴミ箱側に切替\n- `created_at DESC` で降順\n- ステータス値: `draft` / `scheduled` / `sending` / `sent`\n\n### 関連\n- `POST /v1/admin/user-notifications` — 下書き作成\n- `POST /v1/admin/user-notifications/{id}/send` — FCM 配信キューに enqueue",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "scheduled",
                "sending",
                "sent"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "deleted",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUserNotification"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsList"
      },
      "post": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "通知を下書き作成 / Create Draft Notification",
        "description": "### 用途\nユーザー向けプッシュ通知を `draft`（下書き）または `scheduled`（予約）として作成する。\n実配信は別エンドポイント `POST /{id}/send` で別途トリガする。\n\n### 管理者ポータルでの使用タイミング\n- 「通知メッセージ管理」→「新規通知作成」モーダルの保存ボタン\n- 予約通知の登録（`scheduled_at` を指定）\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 作成時の `status` は常に `draft`\n- `target` の既定値は `all`（全ユーザー）\n- `created_by` に呼び出し管理者の `userId` を保存\n- `scheduled_at` 有無で `admin_activity_logs` の action を `user_notification.schedule` / `user_notification.create` に出し分け\n\n### 関連\n- `PATCH /v1/admin/user-notifications/{id}` — 編集\n- `POST /v1/admin/user-notifications/{id}/send` — FCM 配信キューに enqueue",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "type": {
                    "type": "string",
                    "default": "info"
                  },
                  "target": {
                    "type": "string",
                    "default": "all"
                  },
                  "scheduled_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "title",
                  "body"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserNotification"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsCreate"
      }
    },
    "/v1/admin/user-notifications/deleted": {
      "get": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "削除済み一覧 / Trash",
        "description": "### 用途\nソフトデリート済み（`deleted_at IS NOT NULL`）のユーザー向け通知一覧を返す。\nゴミ箱画面で復元 / 完全削除を選択するためのソース。\n\n### 管理者ポータルでの使用タイミング\n- 「通知メッセージ管理」→「ゴミ箱」タブ表示時\n- 復元 / 完全削除アクション後の一覧再取得\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `deleted_at IS NOT NULL` のみ返却\n- `deleted_at DESC` で削除が新しい順\n\n### 関連\n- `POST /v1/admin/user-notifications/{id}/restore` — 復元\n- `DELETE /v1/admin/user-notifications/{id}/purge` — 完全削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUserNotification"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsDeletedList"
      }
    },
    "/v1/admin/user-notifications/{id}/send": {
      "post": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "通知を配信 / Send Notification",
        "description": "### 用途\n指定通知を全 `user_push_tokens` 宛に FCM 配信する。実送信は同期実行せず、\n`parky-fcm-dispatch` キュー（Cloudflare Queues）に 500 件ずつバッチ投入し、\nconsumer (`queue/fcm-dispatch.ts`) が後段で実配信・成否カウント加算を行う。\n\n### 管理者ポータルでの使用タイミング\n- 通知詳細パネルの「今すぐ送信」ボタン\n- 予約通知のスケジュールから手動トリガする時\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 即時 202 を返す（`{notification_id, enqueued, batches}`）\n- バッチサイズは 500 トークン / メッセージ\n- enqueue 前に集計カラム（`status='sending'`, `sent_at=NOW()`, `total_recipients`, `success_count=0`, `failure_count=0`）を初期化\n- 全バッチ処理完了後に consumer が `status='sent'` に更新\n- FCM 認証情報未設定時は 503 `fcm_not_configured`\n- `FCM_DISPATCH_QUEUE` binding 未設定時は 503 `queue_not_configured`\n- 成功時に `admin_activity_logs` へ `user_notification.send`（enqueued 件数 / batches 数）を記録\n\n### 関連\n- `PATCH /v1/admin/user-notifications/{id}` — 配信前の編集\n- queue consumer — `api/src/queue/fcm-dispatch.ts`",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "202": {
            "description": "キューに投入完了。実配信は consumer が行う",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DispatchEnqueued"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "FCM 未設定 or キュー未設定",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "code",
                        "message"
                      ]
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsSend"
      }
    },
    "/v1/admin/user-notifications/{id}": {
      "patch": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "通知を更新 / Update Notification",
        "description": "### 用途\n下書き / 予約状態のユーザー向け通知を部分更新する。タイトル・本文・配信対象・予約時刻などの\n編集に使う。配信済み通知に対しても受け付けるが、メタ情報の修正用途を想定。\n\n### 管理者ポータルでの使用タイミング\n- 通知詳細パネル / 編集モーダルの「保存」\n- 予約時刻の変更\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 空ボディは現在値を返す（no-op）\n- `UserNotificationUpdateSchema` で許可列のみ受け付け（未知キーは silent drop）\n- 存在しない ID は 404 `not_found`\n- 成功時に `admin_activity_logs` へ `user_notification.update`（diff 含む）を記録\n\n### 関連\n- `POST /v1/admin/user-notifications/{id}/send` — 配信キュー投入",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserNotificationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserNotification"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsUpdate"
      },
      "delete": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "ソフト削除 / Soft Delete",
        "description": "### 用途\nユーザー向け通知を論理削除（`deleted_at = NOW()`）し、ゴミ箱へ移動する。物理削除はしない。\n\n### 管理者ポータルでの使用タイミング\n- 通知一覧の行削除アクション\n- 通知詳細パネルの「削除」ボタン\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 一覧 API からは以降返らなくなり、ゴミ箱一覧 (`GET /deleted`) で参照可能\n- 存在しない ID でも 204（冪等）\n- 成功時に `admin_activity_logs` へ `user_notification.delete`（`soft: true`）を記録\n\n### 関連\n- `POST /v1/admin/user-notifications/{id}/restore` — 復元\n- `DELETE /v1/admin/user-notifications/{id}/purge` — 完全削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsDelete"
      }
    },
    "/v1/admin/user-notifications/{id}/restore": {
      "post": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "復元 / Restore",
        "description": "### 用途\nゴミ箱の通知を `deleted_at = NULL` に戻して通常一覧へ復帰させる。\n\n### 管理者ポータルでの使用タイミング\n- ゴミ箱一覧の「復元」ボタン\n- 誤削除のリカバリ操作\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `deleted_at = NULL` を設定するのみ。`status` 等は変更しない\n- 削除されていない ID でも 200（冪等）\n- 成功時に `admin_activity_logs` へ `user_notification.restore` を記録\n\n### 関連\n- `DELETE /v1/admin/user-notifications/{id}` — ソフト削除\n- `DELETE /v1/admin/user-notifications/{id}/purge` — 完全削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsRestore"
      }
    },
    "/v1/admin/user-notifications/{id}/purge": {
      "delete": {
        "tags": [
          "ユーザー向け通知 / User Notifications"
        ],
        "summary": "完全削除 / Purge",
        "description": "### 用途\nゴミ箱の通知を物理削除する。`DELETE FROM user_notifications` を実行し、\n復元不可。コンプライアンス上の完全削除依頼や、テストデータ整理に使う。\n\n### 管理者ポータルでの使用タイミング\n- ゴミ箱一覧の「完全に削除」ボタン（確認ダイアログ後）\n- 「ゴミ箱を空にする」一括操作の内部呼び出し\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 物理削除（`DELETE FROM`）。FK 制約があれば失敗する\n- 存在しない ID でも 204（冪等）\n- 成功時に `admin_activity_logs` へ `user_notification.purge`（`soft: false`）を記録\n\n### 関連\n- `POST /v1/admin/user-notifications/{id}/restore` — 復元（purge 前のみ可）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserNotificationsPurgeDelete"
      }
    },
    "/v1/admin/reviews": {
      "get": {
        "tags": [
          "レビュー / Reviews"
        ],
        "summary": "レビュー一覧（モデレーション）/ Review List (Moderation)",
        "description": "### 用途\nユーザーが投稿した駐車場レビュー（`parking_reviews`）の一覧をモデレーション目的で返す。\nステータス（pending / approved / rejected / hidden）と駐車場 ID で絞り込める。\n\n### 管理者ポータルでの使用タイミング\n- 「駐車場管理 > レビュー管理」画面の初期表示・ページ送り\n- 「未承認」タブで `status=pending` のレビューを順次審査するとき\n- 駐車場詳細から「この駐車場のレビュー一覧」を開いたとき\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`created_at DESC` で並ぶ。ソフトデリート (`deleted_at IS NOT NULL`) も含めて返るので、\n画面側で必要に応じてフィルタする。総件数は別 COUNT クエリで取得。\n\n### 関連\n- `POST /v1/admin/reviews` — 管理者代理投稿\n- `PATCH /v1/admin/reviews/{id}` — 承認・却下・非公開化\n- `DELETE /v1/admin/reviews/{id}` — ソフト削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/ReviewStatus"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "id",
                "user_name",
                "rating",
                "comment",
                "created_at",
                "status"
              ]
            },
            "required": false,
            "name": "order_by",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "required": false,
            "name": "order_dir",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminReview"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminReviewsList"
      },
      "post": {
        "tags": [
          "レビュー / Reviews"
        ],
        "summary": "レビューを作成 / Create Review",
        "description": "### 用途\nメール・お問い合わせフォーム等で受け取ったユーザーレビューを管理者が代理入力するための\nエンドポイント。アプリからの直接投稿と同じ `parking_reviews` テーブルに INSERT する。\n\n### 管理者ポータルでの使用タイミング\n- 「レビュー管理」画面の「新規作成」ボタン\n- ライターによる体験レビュー記事の本文を駐車場ページに転記するとき\n- 監査・テストデータ投入\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\nbody は `passthrough` でそのまま INSERT に流す。`status` のデフォルトは `approved`\n（管理者が代理入力する前提なので即公開）。`rating` は 1〜5 の整数。\n成功時は `admin_activity_logs` に `review.create` として記録される。\n\n### 関連\n- `PATCH /v1/admin/reviews/{id}` — ステータスや管理メモを後で編集",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parking_lot_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "user_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "rating": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5
                  },
                  "comment": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/ReviewStatus"
                      },
                      {
                        "default": "approved"
                      }
                    ]
                  },
                  "admin_note": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "parking_lot_id",
                  "rating"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminReview"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminReviewsCreate"
      }
    },
    "/v1/admin/reviews/{id}": {
      "patch": {
        "tags": [
          "レビュー / Reviews"
        ],
        "summary": "レビューを承認・却下 / Approve / Reject Review",
        "description": "### 用途\nレビューの **モデレーション状態遷移**（`pending` → `approved` / `rejected` / `hidden`）と\n管理者向けメモ (`admin_note`) の編集を行う。\n\n### 管理者ポータルでの使用タイミング\n- 「レビュー管理」一覧で承認 / 却下ボタンを押したとき\n- 不適切判定後に `hidden` にして公開停止するとき\n- 内部メモを残してから一旦保留 (`pending` のまま) するとき\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`status` 取り得る値は `pending` / `approved` / `rejected` / `hidden`（z.enum で検証）。\n空 body は現行値を返すだけ。更新成功時は `admin_activity_logs` に `review.update` として\ndiff と最新ステータスを記録する。\n\n### 関連\n- `DELETE /v1/admin/reviews/{id}` — ソフト削除（`hidden` よりさらに強い措置）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "$ref": "#/components/schemas/ReviewStatus"
                  },
                  "admin_note": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminReview"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminReviewsUpdate"
      },
      "delete": {
        "tags": [
          "レビュー / Reviews"
        ],
        "summary": "レビューを削除 / Delete Review",
        "description": "### 用途\nレビューを **ソフトデリート**（`deleted_at = NOW()`）する。物理削除はしない（履歴と監査の\nため痕跡を残す）。`hidden` よりも強い、最終的な「非表示＋削除済み」措置。\n\n### 管理者ポータルでの使用タイミング\n- 「レビュー管理」一覧 / 詳細でゴミ箱アイコンをクリックしたとき\n- 利用規約違反・誹謗中傷等の通報対応で完全に取り下げる場合\n- バッチでスパム判定された一連のレビューを削除するとき\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n存在しない `id` を指定しても 204 を返す（冪等）。`admin_activity_logs` に\n`review.delete` として `soft: true` メタで記録する。再公開はゴミ箱画面から\n`deleted_at` をクリアする運用を別途用意する。\n\n### 関連\n- `PATCH /v1/admin/reviews/{id}` — ソフト削除より弱い `hidden` への状態遷移\n\n### reason 必須化 (2026-05-11)\n破壊的 (ソフト削除) のため body に `reason` (10〜500 文字) が必須。\nadmin_activity_logs.reason に保存される。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "破壊的操作 (delete / bulk update / role change 等) の理由。 10〜500 文字。 admin_activity_logs.reason 列に保存され、AdminActivityPage で参照される。",
                    "examples": [
                      "ユーザーから利用規約違反の通報を受けたため"
                    ]
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "成功"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminReviewsDelete"
      }
    },
    "/v1/admin/support/tickets": {
      "get": {
        "tags": [
          "サポート / Support"
        ],
        "summary": "サポートチケット一覧 / Support Ticket List",
        "description": "### 用途\nユーザーから受け付けた **サポートチケット**（`support_tickets`）の一覧をページング付きで返す。\n\n### ⚠️ DEPRECATED\n`GET /tickets/cursor` に移行してください。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/SupportTicketStatus"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/SupportTicketPriority"
            },
            "required": false,
            "name": "priority",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminSupportTicket"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSupportTicketsList"
      }
    },
    "/v1/admin/support/tickets/cursor": {
      "get": {
        "tags": [
          "サポート / Support"
        ],
        "summary": "サポートチケット一覧（Cursor）/ Support Ticket List (Cursor)",
        "description": "### 用途\n`support_tickets` を cursor ベースで取得する。`created_at DESC` keyset。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/SupportTicketStatus"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/SupportTicketPriority"
            },
            "required": false,
            "name": "priority",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminSupportTicket"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSupportTicketsCursorList"
      }
    },
    "/v1/admin/support/tickets/{id}": {
      "get": {
        "tags": [
          "サポート / Support"
        ],
        "summary": "チケット詳細 / Ticket Detail",
        "description": "サポートチケット 1 件を `id` で取得する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "1件",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSupportTicket"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSupportTicketsGet"
      },
      "patch": {
        "tags": [
          "サポート / Support"
        ],
        "summary": "チケットを更新 / Update Ticket",
        "description": "### 用途\nサポートチケットの status / priority / 担当 / メモ等を部分更新する。\n空 body の場合は更新せずに現行値を返す（冪等動作）。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SupportTicketUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSupportTicket"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSupportTicketsUpdate"
      }
    },
    "/v1/admin/support/tickets/bulk-status": {
      "patch": {
        "tags": [
          "サポート / Support"
        ],
        "summary": "ステータスを一括変更 / Bulk Update Status",
        "description": "複数のサポートチケットのステータスをまとめて切り替える。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "status": {
                    "$ref": "#/components/schemas/SupportTicketStatus"
                  }
                },
                "required": [
                  "ids",
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSupportTicketsBulkStatusUpdate"
      }
    },
    "/v1/admin/error-reports": {
      "get": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "誤情報報告一覧 / Error Report List",
        "description": "### 用途\nユーザーから提出された **誤情報報告**（`error_reports`）の一覧。投稿ユーザー (`app_users`)\nと対象駐車場 (`parking_lots`) を LEFT JOIN して、画面表示に必要なメタを 1 リクエストで返す。\n\n### ⚠️ DEPRECATED\n**このエンドポイントは 2027-01-01 に廃止予定**。`GET /cursor` に移行してください。\n`analytics.error_reports` は蓄積型なので深い OFFSET が遅くなる。\n\n### 管理者ポータルでの使用タイミング\n- 「エラーレポート」画面の初期表示・ページング\n- ステータス（new / triaged / resolved 等）でのフィルタ切替\n- ダッシュボードの未対応件数バッジから遷移したとき\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`er.created_at DESC` で並ぶ。報告者・駐車場が論理削除済みでも JOIN は行うが、\n存在しない場合は `user` / `parking_lot` を `null` で返す。\n\n### 関連\n- `GET /v1/admin/error-reports/cursor` — cursor 版（新）\n- `GET /v1/admin/error-reports/{id}` — 詳細（駐車場 join 付き）\n- `PATCH /v1/admin/error-reports/{id}` — 1 件更新\n- `PATCH /v1/admin/error-reports/bulk` — 一括更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "report_type",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（deprecated / offset）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminErrorReport"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsList"
      },
      "post": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "誤情報報告を作成 / Create Error Report",
        "description": "メール・電話などアプリ外で受け取った誤情報指摘を管理者が代理入力する。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": "string",
                    "minLength": 1
                  },
                  "user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "user_email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "user_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "parking_lot_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "parking_lot_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "report_type": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "severity": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "evidence_urls": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    }
                  },
                  "resolved_by": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "admin_note": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "resolved_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "memo": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "description"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminErrorReport"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsCreate"
      }
    },
    "/v1/admin/error-reports/cursor": {
      "get": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "誤情報報告一覧（Cursor）/ Error Report List (Cursor)",
        "description": "### 用途\n`analytics.error_reports` を cursor ベースで取得する。深いページでも O(log n)。\n`offset` 版 `GET /` は 2027-01-01 廃止予定。\n\n### cursor の使い方\n1. 初回は `cursor` パラメータを省略。\n2. レスポンスの `next_cursor` が `null` でなければ、次のリクエストに `cursor=<value>` を渡す。\n3. `has_more=false` になったら終端。\n\n### フィルタ\n- `status`: ステータス完全一致\n- `report_type`: 報告種別完全一致\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "report_type",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminErrorReport"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsCursorList"
      }
    },
    "/v1/admin/error-reports/{id}": {
      "get": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "誤情報報告詳細 / Error Report Detail",
        "description": "誤情報報告 1 件の詳細。parking_lot_id が紐づいている場合は対象駐車場情報を parking_lot に同梱する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "1 件",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AdminErrorReport"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "parking_lot": {}
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsGet"
      },
      "patch": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "報告を更新 / Update Report",
        "description": "誤情報報告のステータス・管理メモ・対応結果などを部分更新する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ErrorReportUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminErrorReport"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsUpdate"
      }
    },
    "/v1/admin/error-reports/bulk": {
      "patch": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "ステータス・担当を一括変更 / Bulk Update Status & Assignee",
        "description": "### 用途\n複数の誤情報報告に対して同一の `patch` をまとめて適用する。担当者の一括アサイン、\nステータスの一括クローズ、優先度変更などをまとめて行うための汎用一括 API。\n\n### 管理者ポータルでの使用タイミング\n- 「エラーレポート」一覧で複数行をチェック → 「一括操作」メニューから実行\n- スパム判定された一連のレポートをまとめて `closed` にするとき\n- 担当者を一括で別管理者に付け替えるとき\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`ids` が空なら 0 件で即 return。`patch` は `passthrough` で受け取り、そのまま UPDATE に流す\n（呼び出し側がカラム名を保証する責任を持つ）。`admin_activity_logs` に\n`error_report.bulk_update` として ids / patch / 件数を記録する。\n\n### 関連\n- `PATCH /v1/admin/error-reports/{id}` — 単票更新",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "patch": {
                    "$ref": "#/components/schemas/ErrorReportUpdate"
                  }
                },
                "required": [
                  "ids",
                  "patch"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsBulkUpdate"
      }
    },
    "/v1/admin/error-reports/{id}/action": {
      "post": {
        "tags": [
          "誤情報報告 / Error Reports"
        ],
        "summary": "運営アクションを実行 / Execute Admin Action",
        "description": "### 用途\n誤情報報告 1 件に対して管理者が取った運営対応を記録する。\n`mark_closed` / `hide_lot` のみ副作用あり（`parking_lots.status` を `on_hold` に変更）。\nその他は `admin_activity_logs` への記録のみ。\n\n### アクション種別\n- `hide_lot` — 駐車場を非表示扱い（`on_hold`）\n- `mark_closed` — 閉鎖報告を受けて駐車場を `on_hold` に更新\n- `request_owner_confirm` — オーナーへ確認依頼（ログのみ）\n- `noop` — 調査の結果対応不要と判断（ログのみ）\n\n### 挙動・制約\nparking_lots.status に `closed` コードが存在しないため `on_hold` で代替する。\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 関連\n- `PATCH /v1/admin/error-reports/{id}` — ステータス・メモの部分更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "hide_lot",
                      "mark_closed",
                      "request_owner_confirm",
                      "noop"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 1000
                  }
                },
                "required": [
                  "action"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "アクション完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "action": {
                      "type": "string"
                    },
                    "report_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "action",
                    "report_id"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminErrorReportsActionCreate"
      }
    },
    "/v1/admin/tasks": {
      "get": {
        "tags": [
          "タスク / Tasks"
        ],
        "summary": "タスク一覧 / Task List",
        "description": "### 用途\nサポート / 誤情報 / オーナー申請 / 駐車場新規登録など、種類の異なる管理者タスクを\n**1 つのインボックス**として横断的に返す。`admin_tasks` テーブルが各種別の `ref_id` で\n元レコードを指す統合ビューになっている。\n\n### ⚠️ DEPRECATED\n**このエンドポイントは 2027-01-01 に廃止予定**。`GET /cursor` に移行してください。\n\n### include パラメータ\n`include=resource` を指定すると、`task_kind` 別に参照先テーブル\n(support_tickets / analytics.error_reports / owner_applications / parking_lots) を\ncore 側で LEFT JOIN し、各レコードに `resource: { title, subtitle, status }` を埋めて返す。\n\n### unassigned パラメータ\n`unassigned=true` を指定すると `assignee_id IS NULL` の行のみを返す。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/AdminTaskKind"
            },
            "required": false,
            "name": "task_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "assignee_id",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/AdminTaskUrgency"
            },
            "required": false,
            "name": "urgency",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "unassigned",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "resource"
              ]
            },
            "required": false,
            "name": "include",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminTask"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTasksList"
      }
    },
    "/v1/admin/tasks/cursor": {
      "get": {
        "tags": [
          "タスク / Tasks"
        ],
        "summary": "タスク一覧（Cursor）/ Task List (Cursor)",
        "description": "### 用途\n`admin.admin_tasks` を cursor ベースで取得する。`updated_at DESC` keyset。\noffset 版 `GET /` は 2027-01-01 廃止予定。\n\n### cursor の使い方\n1. 初回は `cursor` パラメータを省略。\n2. レスポンスの `next_cursor` が `null` でなければ、次のリクエストに `cursor=<value>` を渡す。\n3. `has_more=false` になったら終端。\n\n### フィルタ\n- `task_kind`: タスク種別完全一致\n- `assignee_id`: 担当者 UUID 完全一致\n- `urgency`: 緊急度完全一致\n- `unassigned=true`: `assignee_id IS NULL` の行のみ\n\n### include パラメータ\n`include=resource` で `resource: { title, subtitle, status }` を core 側 JOIN で埋める。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/AdminTaskKind"
            },
            "required": false,
            "name": "task_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "assignee_id",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/AdminTaskUrgency"
            },
            "required": false,
            "name": "urgency",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "unassigned",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "resource"
              ]
            },
            "required": false,
            "name": "include",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminTask"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTasksCursorList"
      }
    },
    "/v1/admin/tasks/{id}": {
      "patch": {
        "tags": [
          "タスク / Tasks"
        ],
        "summary": "タスクを更新 / Update Task",
        "description": "### 用途\n管理タスク 1 件の担当者・期日・緊急度・内部メモを部分更新する。元レコード（チケット・\n報告等）には触らず、タスク管理層だけを更新するため動作が軽い。\n\n### 挙動・制約\n空 body は現行値を返すだけの no-op。存在しない `id` は `not_found` (404)。\n成功時は `admin_activity_logs` に `admin_task.update` として diff と `task_kind` を記録する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminTaskUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminTask"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTasksUpdate"
      }
    },
    "/v1/admin/tasks/by-ref": {
      "put": {
        "tags": [
          "タスク / Tasks"
        ],
        "summary": "タスクを参照IDで作成・更新 / Upsert Task by Ref",
        "description": "### 用途\n元レコード（サポートチケット / 誤情報報告 / オーナー申請 / 駐車場新規登録）の `id` を\n`ref_id` として渡し、対応する `admin_tasks` 行を **upsert** する。タスクが既に存在すれば\n指定フィールドのみ COALESCE で上書きし、未指定は現行値を維持する。\n\n### 挙動・制約\nユニークキーは `(task_kind, ref_id)`。`urgency` 未指定時は `medium`、それ以外は\nCOALESCE で現行値維持。`updated_at` は常に `NOW()` で更新。`admin_activity_logs` に\n`admin_task.upsert_by_ref` として記録する。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "task_kind": {
                    "$ref": "#/components/schemas/AdminTaskKind"
                  },
                  "ref_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "assignee_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "urgency": {
                    "$ref": "#/components/schemas/AdminTaskUrgency"
                  },
                  "due_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "memo": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "task_kind",
                  "ref_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "upsert 後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminTask"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminTasksByRefReplace"
      }
    },
    "/v1/admin/articles": {
      "get": {
        "tags": [
          "記事 / Articles"
        ],
        "summary": "記事一覧 / Article List",
        "description": "### 用途\n管理者向けに `articles` テーブルの記事を一覧する。公開済み（`published`）だけでなく\n下書き（`draft`）・予約・非公開も含めた全ステータスを返し、タイトル ILIKE / カテゴリ /\nステータスで絞り込める。並びは `updated_at DESC`。\n\n### 管理者ポータルでの使用タイミング\n- コンテンツ管理 > 記事管理 のメイン一覧テーブルを開いたとき\n- ステータスタブ（公開中 / 下書き / 予約）切替時\n- 検索ボックスへのキーワード入力（タイトル ILIKE 部分一致）\n- カテゴリドロップダウンでフィルタしたとき\n\n### 認証・認可\n`requireAdmin`。管理者ホワイトリストに登録された Auth ユーザーのみアクセス可。\n\n### 挙動・制約\n- ページング: `PageQuerySchema`（page / limit）\n- `total` は別 COUNT クエリで返却（同一フィルタで集計）\n- ソフトデリート列は持たないため物理削除前提\n\n### 関連\n- `POST /v1/admin/articles` — 新規作成\n- `PATCH /v1/admin/articles/{slug}` — 部分更新\n- `DELETE /v1/admin/articles/{slug}` — 物理削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminArticle"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminArticlesList"
      },
      "post": {
        "tags": [
          "記事 / Articles"
        ],
        "summary": "記事を作成 / Create Article",
        "description": "### 用途\n新規記事を `articles` テーブルに INSERT する。本文・カテゴリ・タグ・サムネイル URL 等を\nボディで自由に渡す（`passthrough`）。`status` を `draft` で作って後から `published` に\n切り替える運用を想定。\n\n### 管理者ポータルでの使用タイミング\n- コンテンツ管理 > 記事管理 の「新規作成」ボタン押下時\n- 一覧から複製（コピー＆編集）した直後の保存時\n- 外部 CMS / インポーター経由の自動取り込み\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- ボディは `passthrough` のため列名と一致するキーだけ採用される（タイポはそのまま列指定）\n- 作成成功時に `recordAdminActivityBestEffort` で `article.create` 監査ログを残す\n- 画像アップロードは別 API（assets 系）で先に行い、`thumbnail_url` をここで保存\n\n### 関連\n- `GET /v1/admin/articles` — 一覧\n- `PATCH /v1/admin/articles/{slug}` — 公開ステータス変更等",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArticleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminArticle"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminArticlesCreate"
      }
    },
    "/v1/admin/articles/{slug}": {
      "get": {
        "tags": [
          "記事 / Articles"
        ],
        "summary": "記事単体取得（タグ含む / natural key = articles.slug）",
        "description": "### 用途\n指定 slug の記事を 1 件取得する。タグも JOIN 済みで返却するので、\n編集画面の初期表示で `GET /v1/admin/articles?limit=...` → client 側 find の\n迂回ロジックを排除できる。\n\n### 管理者ポータルでの使用タイミング\n- 記事編集画面（`ArticleDetailPage` / `ArticleEditorPage`）の初期ロード\n- プレビュー / 差し戻し確認モーダルの表示\n\n### 認証・認可\n`requireAdmin`（list と同じ）。\n\n### 挙動・制約\n- path param `{slug}` は `articles.slug` (natural key, UNIQUE) を指定\n- articles テーブルはソフトデリート列を持たないため、単純に slug 一致で引く\n- 該当 slug が無い場合は `not_found`（404）\n- 返却 shape は list 各要素と同一（タグを article_tag_links 経由で集約）\n\n### 関連\n- `GET /v1/admin/articles` — 一覧\n- `PATCH /v1/admin/articles/{slug}` — 更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200,
              "pattern": "^[a-z0-9_\\-/]+$"
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "記事詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminArticle"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminArticlesGet"
      },
      "patch": {
        "tags": [
          "記事 / Articles"
        ],
        "summary": "記事を更新（natural key = articles.slug）",
        "description": "### 用途\n既存記事を部分更新する。リクエストボディに含まれた列だけが UPDATE 対象（postgres.js の\n`sql(body)` ヘルパー）。空ボディの場合は現行レコードをそのまま返す。\n\n### 管理者ポータルでの使用タイミング\n- 記事編集画面の「保存」ボタン押下時\n- 一覧画面でステータスをインライン切替したとき（draft ↔ published）\n- カテゴリ・タグ一括編集モーダルからのコミット\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- path param `{slug}` は `articles.slug` (natural key, UNIQUE) を指定\n- 公開フラグ: `status = 'published'` のとき `published_at` をボディ側で同時にセットする運用\n- 該当 slug が無い場合は `not_found`（404）\n- 監査ログ `article.update` に diff（送信ボディ）を記録\n\n### 関連\n- `POST /v1/admin/articles` — 新規作成\n- `DELETE /v1/admin/articles/{slug}` — 削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200,
              "pattern": "^[a-z0-9_\\-/]+$"
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArticleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminArticle"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminArticlesUpdate"
      },
      "delete": {
        "tags": [
          "記事 / Articles"
        ],
        "summary": "記事を削除（natural key = articles.slug）",
        "description": "### 用途\n記事を物理削除する（`DELETE FROM articles`）。ソフトデリート列は持たないため復元不可。\n誤削除を防ぐためポータル側で確認モーダルを必ず挟むこと。\n\n### 管理者ポータルでの使用タイミング\n- 記事編集画面の「削除」ボタン → 確認モーダル承認時\n- 一覧画面の行アクションメニュー「削除」\n- 複数選択での一括削除（ループ呼び出し）\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- path param `{slug}` は `articles.slug` (natural key, UNIQUE) を指定\n- 物理削除（履歴テーブルや関連テーブルへの cascade は DB スキーマに依存）\n- 監査ログ `article.delete` にタイトルを残す（削除前に SELECT して保存）\n- 存在しない slug でも 204 を返す（冪等）\n\n### 関連\n- `PATCH /v1/admin/articles/{slug}` — ソフトに非公開化したい場合は `status` を変える",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200,
              "pattern": "^[a-z0-9_\\-/]+$"
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminArticlesDelete"
      }
    },
    "/v1/admin/article-tags": {
      "get": {
        "tags": [
          "記事タグ / Article Tags"
        ],
        "summary": "記事タグ一覧 / Article Tag List",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminArticleTag"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminArticleTagsList"
      }
    },
    "/v1/admin/ads": {
      "get": {
        "tags": [
          "広告 / Ads"
        ],
        "summary": "広告一覧 / Ad List",
        "description": "### 用途\nアプリ内バナー広告 (`ads`) の管理一覧。配信ステータス・名前 ILIKE で絞り込みでき、\nインプレッション / クリック数も同時に返す。\n\n### 認証・認可\n`requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminAd"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdsList"
      },
      "post": {
        "tags": [
          "広告 / Ads"
        ],
        "summary": "広告を作成 / Create Ad",
        "description": "新規広告枠を `ads` テーブルに登録する。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAd"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdsCreate"
      }
    },
    "/v1/admin/ads/{id}": {
      "get": {
        "tags": [
          "広告 / Ads"
        ],
        "summary": "広告詳細 / Ad Detail",
        "description": "### 用途\n指定 ID の広告レコードを 1 件返す。list の `limit` 超過で拾えないケースを避けるため、\n編集画面初期ロードは list ではなく本 endpoint を使うこと。\n\n### 認証・認可\n`requireAdmin`（list と同じ）。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "広告詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAd"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdsGet"
      },
      "patch": {
        "tags": [
          "広告 / Ads"
        ],
        "summary": "広告を更新 / Update Ad",
        "description": "広告レコードを部分更新する。空ボディなら現行値をそのまま返却。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAd"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdsUpdate"
      },
      "delete": {
        "tags": [
          "広告 / Ads"
        ],
        "summary": "広告を削除 / Delete Ad",
        "description": "広告を物理削除する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAdsDelete"
      }
    },
    "/v1/admin/boosts": {
      "get": {
        "tags": [
          "ブースト / Boosts"
        ],
        "summary": "ブースト一覧 / Boost List",
        "description": "### 用途\nオーナーが特定駐車場の検索順位を上げる「ブースト」キャンペーンの管理一覧。\n`owners` と `parking_lots` を LEFT JOIN して画面表示に必要な属性をまとめて返す。\n\n### 認証・認可\n`requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "owner_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminBoost"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBoostsList"
      },
      "post": {
        "tags": [
          "ブースト / Boosts"
        ],
        "summary": "ブーストを作成 / Create Boost",
        "description": "新規ブーストキャンペーンを作成する。`started_at` は必須、`ended_at` は任意。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoostUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminBoost"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBoostsCreate"
      }
    },
    "/v1/admin/boosts/{id}": {
      "patch": {
        "tags": [
          "ブースト / Boosts"
        ],
        "summary": "ブーストを更新 / Update Boost",
        "description": "ブーストの予算 / ステータス / 終了日時等を部分更新する。空ボディは現行値を返す。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoostUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminBoost"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBoostsUpdate"
      }
    },
    "/v1/admin/sponsors": {
      "get": {
        "tags": [
          "スポンサー / Sponsors"
        ],
        "summary": "スポンサー一覧 / Sponsor List",
        "description": "### 用途\nエリアスポンサー (`area_sponsors`) の管理一覧。`include_deleted=true` でゴミ箱表示も可能。\n\n### 認証・認可\n`requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminSponsor"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSponsorsList"
      },
      "post": {
        "tags": [
          "スポンサー / Sponsors"
        ],
        "summary": "スポンサーを作成 / Create Sponsor",
        "description": "新規スポンサーを `area_sponsors` に登録する。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SponsorUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSponsor"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSponsorsCreate"
      }
    },
    "/v1/admin/sponsors/{id}": {
      "patch": {
        "tags": [
          "スポンサー / Sponsors"
        ],
        "summary": "スポンサーを更新 / Update Sponsor",
        "description": "スポンサーレコードを部分更新する。空ボディは現行値返却。`deleted_at: null` 明示で復元可能。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SponsorUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSponsor"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSponsorsUpdate"
      },
      "delete": {
        "tags": [
          "スポンサー / Sponsors"
        ],
        "summary": "スポンサーをソフト削除 / Soft Delete Sponsor",
        "description": "スポンサーを論理削除（`deleted_at = NOW()`）する。物理削除はしない。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSponsorsDelete"
      }
    },
    "/v1/admin/owners": {
      "get": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナー一覧 / Owner List",
        "description": "### 用途\n駐車場オーナー (`owners`) の一覧を返す。各オーナーが管理する `parking_lots` の件数も\n`parking_count` として同梱する。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "owner_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "id",
                "owner_type",
                "name",
                "company_name",
                "email",
                "phone",
                "status",
                "created_at"
              ]
            },
            "required": false,
            "name": "order_by",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "required": false,
            "name": "order_dir",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOwner"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersList"
      },
      "post": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナーを作成 / Create Owner",
        "description": "### 用途\nオーナーを管理者画面から手動作成する（申請フローを経由しない）。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OwnerUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwner"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersCreate"
      }
    },
    "/v1/admin/owners/{id}/parking-lots": {
      "get": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナーの管理駐車場一覧 / Owner Managed Lots",
        "description": "### 用途\n指定オーナーが紐づく `parking_lot_owners` 経由の駐車場一覧を返す。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "parking_lot_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "parking_lots": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "address": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "address",
                          "status"
                        ]
                      }
                    },
                    "required": [
                      "parking_lot_id",
                      "parking_lots"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersParkingLotsList"
      }
    },
    "/v1/admin/owners/{id}": {
      "patch": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナーを更新 / Update Owner",
        "description": "### 用途\nオーナーレコードを部分更新する。\n空 body の場合は現在値を返すだけ（no-op）。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OwnerUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwner"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersUpdate"
      }
    },
    "/v1/admin/owners/{id}/password-reset": {
      "post": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナーのパスワード設定リンクを再発行（管理者操作）",
        "description": "### 用途\nオーナー本人がパスワードを失念した / セキュリティ事象で強制リセットしたい場合に\n管理者画面から呼び出す。owner_password_setup_tokens に `purpose='reset'` で 24h 期限の\nトークンを発行し、メールで設定 URL を送付する。\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n- owners.user_id が NULL の場合は 409（その場合は招待フローでやり直す）\n- owners.status が active 以外の場合は 409\n- 既存の有効トークンは全て invalidate されてから新規 1 本が INSERT される\n- メール送信失敗時もトークンは DB に残る（再送 UI で復旧可能）\n- `admin_activity_logs` に `owner.password_reset` を記録\n\n### 関連\n- `POST /v1/admin/owner-registrations/{id}/approve` — 申込承認時の初回招待発行\n- `POST /v1/owner-public/password-setup/complete` — オーナー本人がトークンを消費して PW 確定",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "送信結果（メール送信ステータスを含む）",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwnerPasswordResetResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersPasswordResetCreate"
      }
    },
    "/v1/admin/owners/{id}/password-tokens": {
      "get": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナーパスワード設定トークン履歴",
        "description": "### 用途\n1 オーナーに対して発行されたパスワード設定 / リセットリンクの直近 10 件履歴を返す。\n管理者画面で「招待がうまく届いたか」「直近で再送したか」を一目で判定するため。\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n- 並び順は `created_at DESC` 固定、最大 10 件\n- `state` は SQL 側で導出: `consumed` / `used` / `invalidated` / `expired` / `active`\n- `consumed_at` は single-use 強制 (2026-04-27) で立つ列、旧 `used_at` は historical\n- `token_hash` 等の機微情報は返さない",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "履歴",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOwnerPasswordTokenHistoryItem"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersPasswordTokensList"
      }
    },
    "/v1/admin/owners/credits": {
      "get": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "全オーナーのクレジット残高一覧 / All Owner Credit Balances",
        "description": "### 用途\n全オーナーの `owner_credits` 残高を一覧する。`owners` を LEFT JOIN し、\nオーナー名/メール/種別を同梱して残高 DESC で返す。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOwnerCredit"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersCreditsList"
      }
    },
    "/v1/admin/owners/{id}/credit": {
      "get": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "オーナーのクレジット残高 / Owner Credit Balance",
        "description": "### 用途\n1 オーナーのクレジット残高 (`owner_credits`) を返す。レコードが無い場合は\n`{ balance: 0, total_purchased: 0, total_consumed: 0 }` のゼロ初期値を返す。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "残高",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "owner_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    },
                    "balance": {
                      "type": "integer"
                    },
                    "total_purchased": {
                      "type": "integer"
                    },
                    "total_consumed": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "owner_id",
                    "balance",
                    "total_purchased",
                    "total_consumed"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersCreditList"
      }
    },
    "/v1/admin/owners/{id}/credit-transactions": {
      "get": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "クレジット取引履歴 / Credit Transaction History",
        "description": "### 用途\nオーナーのクレジット取引履歴 (`credit_transactions`) を時系列降順で返す。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "履歴",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminCreditTransaction"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersCreditTransactionsList"
      },
      "post": {
        "tags": [
          "オーナー / Owners"
        ],
        "summary": "クレジット調整 / Adjust Credit",
        "description": "### 用途\nオーナーのクレジット残高を手動で増減させる。`credit_transactions` に 1 行追加し、\n`owner_credits` を UPSERT で更新する。\n\n### 認証・認可\n要 `requireAdmin`。`owners:adjust_credit` 相当の高権限が必要。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "integer"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "type",
                  "amount"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "追加した取引",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "owner_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "type": {
                      "type": "string"
                    },
                    "amount": {
                      "type": "integer"
                    },
                    "balance_after": {
                      "type": "integer"
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "owner_id",
                    "type",
                    "amount",
                    "balance_after",
                    "description",
                    "created_at"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersCreditTransactionsCreate"
      }
    },
    "/v1/admin/owners/owner-applications": {
      "get": {
        "tags": [
          "authority-requests"
        ],
        "summary": "管理権限申請一覧（ページング + フィルタ）",
        "description": "### 用途\n管理権限申請 (`owner_applications`) の一覧を返す。審査待ち・承認済・却下の\nステータス別に絞り込み、申請者名や駐車場 ID での検索も可能。\n書類アセット (`assets`)・申請者プロフィール・審査者情報を JOIN した表示用フォーマット。\n\n### 管理者ポータルでの使用タイミング\n- オーナー管理 > 申請一覧画面のテーブル表示\n- ダッシュボードの「審査待ち申請」ウィジェットからの遷移\n- 特定駐車場に対する申請を探す検索導線\n\n### 認証・認可\n要 `requireAdmin`。`owners:review` 権限が必要。\n\n### 挙動・制約\n- ページング: `PageQuerySchema`（page/page_size）\n- フィルタ: `status` / `parking_lot_id` / `q`（`owner_name` または `company_name` を ILIKE 部分一致）\n- LEFT JOIN で `parking_lots` / `assets` (proof_asset) / `admins` (reviewer) を添付\n- ソート: `created_at` 降順（新しい申請が上）\n\n### 関連\n- `PATCH /v1/admin/authority-requests/{id}` — ステータス更新（却下・保留等）\n- `POST /v1/admin/authority-requests/{id}/approve` — 承認（オーナー化）\n- `POST /v1/owner/authority-requests` — 申請作成（オーナー側）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOwnerApplication"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersOwnerApplicationsList"
      }
    },
    "/v1/admin/owners/owner-applications/{id}": {
      "patch": {
        "tags": [
          "authority-requests"
        ],
        "summary": "申請ステータスを更新 / Update Application Status",
        "description": "### 用途\n管理権限申請のステータスやメモを部分更新する。主に却下・保留・追加情報要求などの\n審査途中の操作に使う。承認は専用 API（`/approve`）を使うこと。\n\n### 管理者ポータルでの使用タイミング\n- 申請詳細画面で「却下」「差し戻し」「保留」などのアクション\n- `reject_reason`（却下理由）を入力して保存\n- 書類再提出を待つステータスへ遷移\n\n### 認証・認可\n要 `requireAdmin`。`owners:review` 権限が必要。`reviewed_by` に操作者 admin ID が自動記録される。\n\n### 挙動・制約\n- ボディは passthrough で柔軟に受ける（`status` / `reject_reason` など）\n- `reviewed_at = NOW()` を自動セット\n- 承認時の owner 作成ロジックは含まない（それは `/approve` の役目）\n- `admin_activity_logs` に差分が記録される\n\n### 関連\n- `POST /v1/admin/authority-requests/{id}/approve` — 承認（オーナー化）\n- `GET /v1/admin/authority-requests` — 申請一覧",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminOwnerApplicationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwnerApplication"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersOwnerApplicationsUpdate"
      }
    },
    "/v1/admin/owners/owner-applications/{id}/approve": {
      "post": {
        "tags": [
          "authority-requests"
        ],
        "summary": "申請を承認（既存オーナーに駐車場を追加紐付け）",
        "description": "### 用途\n既ログインのアクティブオーナーが追加駐車場の管理権を申請したものを承認し、\n`parking_lot_owners` の紐付けを作成する。\n\n### 管理者ポータルでの使用タイミング\n- 申請詳細画面 →「承認する」ボタン押下（書類確認後）\n\n### 認証・認可\n要 `requireAdmin`。`owners:approve` 相当の高権限が必要。\n\n### 挙動・制約\n- 1 トランザクションで `parking_lot_owners` 追加 + `owner_applications.status='approved'` 更新\n- 申請者は `requireOwner` で認証された既存オーナーのみ（`owner_applications.owner_id` 必須）\n- 新規アカウント発行 / メール送信は発生しない（LP 経由の `/owner-registrations` で扱う）\n- `admin_activity_logs` に `owner_application.approve` を記録\n\n### 関連\n- `PATCH /v1/admin/authority-requests/{id}` — ステータス更新（却下等）\n- `POST /v1/admin/owner-registrations/{id}/approve` — LP 申込からの新規オーナー発行",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "承認結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "application_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "owner_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "required": [
                    "ok",
                    "application_id",
                    "owner_id"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnersOwnerApplicationsApprove"
      }
    },
    "/v1/admin/authority-requests/owner-applications": {
      "get": {
        "tags": [
          "authority-requests"
        ],
        "summary": "管理権限申請一覧（ページング + フィルタ）",
        "description": "### 用途\n管理権限申請 (`owner_applications`) の一覧を返す。審査待ち・承認済・却下の\nステータス別に絞り込み、申請者名や駐車場 ID での検索も可能。\n書類アセット (`assets`)・申請者プロフィール・審査者情報を JOIN した表示用フォーマット。\n\n### 管理者ポータルでの使用タイミング\n- オーナー管理 > 申請一覧画面のテーブル表示\n- ダッシュボードの「審査待ち申請」ウィジェットからの遷移\n- 特定駐車場に対する申請を探す検索導線\n\n### 認証・認可\n要 `requireAdmin`。`owners:review` 権限が必要。\n\n### 挙動・制約\n- ページング: `PageQuerySchema`（page/page_size）\n- フィルタ: `status` / `parking_lot_id` / `q`（`owner_name` または `company_name` を ILIKE 部分一致）\n- LEFT JOIN で `parking_lots` / `assets` (proof_asset) / `admins` (reviewer) を添付\n- ソート: `created_at` 降順（新しい申請が上）\n\n### 関連\n- `PATCH /v1/admin/authority-requests/{id}` — ステータス更新（却下・保留等）\n- `POST /v1/admin/authority-requests/{id}/approve` — 承認（オーナー化）\n- `POST /v1/owner/authority-requests` — 申請作成（オーナー側）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOwnerApplication"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAuthorityRequestsOwnerApplicationsList"
      }
    },
    "/v1/admin/authority-requests/owner-applications/{id}": {
      "patch": {
        "tags": [
          "authority-requests"
        ],
        "summary": "申請ステータスを更新 / Update Application Status",
        "description": "### 用途\n管理権限申請のステータスやメモを部分更新する。主に却下・保留・追加情報要求などの\n審査途中の操作に使う。承認は専用 API（`/approve`）を使うこと。\n\n### 管理者ポータルでの使用タイミング\n- 申請詳細画面で「却下」「差し戻し」「保留」などのアクション\n- `reject_reason`（却下理由）を入力して保存\n- 書類再提出を待つステータスへ遷移\n\n### 認証・認可\n要 `requireAdmin`。`owners:review` 権限が必要。`reviewed_by` に操作者 admin ID が自動記録される。\n\n### 挙動・制約\n- ボディは passthrough で柔軟に受ける（`status` / `reject_reason` など）\n- `reviewed_at = NOW()` を自動セット\n- 承認時の owner 作成ロジックは含まない（それは `/approve` の役目）\n- `admin_activity_logs` に差分が記録される\n\n### 関連\n- `POST /v1/admin/authority-requests/{id}/approve` — 承認（オーナー化）\n- `GET /v1/admin/authority-requests` — 申請一覧",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminOwnerApplicationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwnerApplication"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAuthorityRequestsOwnerApplicationsUpdate"
      }
    },
    "/v1/admin/authority-requests/owner-applications/{id}/approve": {
      "post": {
        "tags": [
          "authority-requests"
        ],
        "summary": "申請を承認（既存オーナーに駐車場を追加紐付け）",
        "description": "### 用途\n既ログインのアクティブオーナーが追加駐車場の管理権を申請したものを承認し、\n`parking_lot_owners` の紐付けを作成する。\n\n### 管理者ポータルでの使用タイミング\n- 申請詳細画面 →「承認する」ボタン押下（書類確認後）\n\n### 認証・認可\n要 `requireAdmin`。`owners:approve` 相当の高権限が必要。\n\n### 挙動・制約\n- 1 トランザクションで `parking_lot_owners` 追加 + `owner_applications.status='approved'` 更新\n- 申請者は `requireOwner` で認証された既存オーナーのみ（`owner_applications.owner_id` 必須）\n- 新規アカウント発行 / メール送信は発生しない（LP 経由の `/owner-registrations` で扱う）\n- `admin_activity_logs` に `owner_application.approve` を記録\n\n### 関連\n- `PATCH /v1/admin/authority-requests/{id}` — ステータス更新（却下等）\n- `POST /v1/admin/owner-registrations/{id}/approve` — LP 申込からの新規オーナー発行",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "承認結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "application_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "owner_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "required": [
                    "ok",
                    "application_id",
                    "owner_id"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAuthorityRequestsOwnerApplicationsApprove"
      }
    },
    "/v1/admin/owner-registrations": {
      "get": {
        "tags": [
          "owner-inquiries"
        ],
        "summary": "オーナー申込一覧",
        "description": "公開 LP (/for-owners/) からの掲載申込を事務局向けに表示するための一覧。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "inquiry_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminOwnerInquiry"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnerRegistrationsList"
      }
    },
    "/v1/admin/owner-registrations/{id}": {
      "get": {
        "tags": [
          "owner-inquiries"
        ],
        "summary": "オーナー申込詳細（紐づく提出書類含む）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwnerInquiryDetail"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnerRegistrationsGet"
      },
      "patch": {
        "tags": [
          "owner-inquiries"
        ],
        "summary": "オーナー申込の部分更新（notes / status / reject_reason）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminOwnerInquiryUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwnerInquiry"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnerRegistrationsUpdate"
      }
    },
    "/v1/admin/owner-registrations/{id}/issue-upload-url": {
      "post": {
        "tags": [
          "owner-inquiries"
        ],
        "summary": "書類アップロード専用 URL を発行 + 通知メール送信",
        "description": "owner_inquiries の status を documents_requested に遷移させ、専用 URL を申請者にメール送付する。既発行の有効トークンは全て invalidated 化される。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "発行成功（メール送信ステータスを含む）",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssueUploadUrlResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnerRegistrationsIssueUploadUrlCreate"
      }
    },
    "/v1/admin/owner-registrations/{id}/approve": {
      "post": {
        "tags": [
          "owner-inquiries"
        ],
        "summary": "申込を承認 → Auth user + public.owners 作成 + 初期パスワード通知",
        "description": "Supabase Auth にユーザー作成、public.owners に upsert、初期パスワードを生成してメール通知する。べき等性は status=approved での 409 で保証。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "承認成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApproveOwnerInquiryResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnerRegistrationsApprove"
      }
    },
    "/v1/admin/owner-registrations/{id}/reject": {
      "post": {
        "tags": [
          "owner-inquiries"
        ],
        "summary": "申込を却下する",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectOwnerInquiryBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "却下済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOwnerInquiry"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOwnerRegistrationsReject"
      }
    },
    "/v1/admin/activity-logs/cursor": {
      "get": {
        "tags": [
          "監査ログ / Audit Logs"
        ],
        "summary": "管理者監査ログ（Cursor）/ Admin Audit Log (Cursor)",
        "description": "cursor ベースで深いページでも O(log n) で取得できる。`GET /` の offset 版は 2026-10-22 廃止予定。\n\n### 対象テーブル\n`admin_activity_logs` のみ。`user_activity_logs` には `/cursor-user` を使う。\n\n### cursor の使い方\n1. 初回は `cursor` パラメータを省略して取得する。\n2. レスポンスの `next_cursor` が `null` でなければ、次のリクエストに `cursor=<value>` を渡す。\n3. `has_more=false` になったら終端。\n\n### フィルタ\n- `q`: `target_label` / `admin_email_snapshot` 横断 ILIKE 検索\n- `action`: action 完全一致\n- `target_type`: target_type 完全一致\n- `admin_id`: UUID\n- `from` / `to`: `created_at` の範囲（ISO 8601）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "target_label / admin_email_snapshot 横断 ILIKE 検索"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "action 完全一致"
            },
            "required": false,
            "name": "action",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "target_type 完全一致"
            },
            "required": false,
            "name": "target_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "admin_id UUID 完全一致",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "admin_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "created_at >= (ISO 8601)"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "created_at <= (ISO 8601)"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "監査ログ一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminActivityLog"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminActivityLogsCursorList"
      }
    },
    "/v1/admin/activity-logs/cursor-user": {
      "get": {
        "tags": [
          "監査ログ / Audit Logs"
        ],
        "summary": "ユーザー行動ログ（Cursor）/ User Activity Log (Cursor)",
        "description": "cursor ベースで `user_activity_logs` を取得する。`admin_activity_logs` には `/cursor` を使う。\n\n### フィルタ\n- `user_id` / `activity_type`\n- `parking_lot_id`（`user_activity_log_targets` 経由で絞り込む）\n- `from` / `to`: `created_at` の範囲（ISO 8601）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "ユーザー UUID",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "activity_type 完全一致"
            },
            "required": false,
            "name": "activity_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "駐車場 UUID（targets 経由）",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "created_at >= (ISO 8601)"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "created_at <= (ISO 8601)"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "ユーザー行動ログ一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminActivityLog"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminActivityLogsCursorUserList"
      }
    },
    "/v1/admin/activity-logs": {
      "post": {
        "tags": [
          "監査ログ / Audit Logs"
        ],
        "summary": "監査ログを追加 / Add Audit Log",
        "description": "### 用途\n管理ポータル側で行われた操作を `admin_activity_logs` に記録する。サーバー側の各 admin API は\n`recordAdminActivityBestEffort` で内部記録するが、フロント独自の操作（ページ遷移・出力等）を\n追記したい場合はこの API を直接呼ぶ。\n\n### 挙動・制約\n- `metadata` は `jsonb` で柔軟構造を許容\n- `target_*` は任意（システムレベル操作なら NULL でも良い）\n- `admin_email_snapshot` は `admins` テーブルから email を引いて固定保存（後で管理者削除されても残る）",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "minLength": 1
                  },
                  "target_type": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "target_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "target_label": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "metadata": {
                    "type": "object",
                    "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
                    "additionalProperties": true
                  }
                },
                "required": [
                  "action"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminActivityLog"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminActivityLogsCreate"
      }
    },
    "/v1/admin/revenue/monthly": {
      "get": {
        "tags": [
          "収益 / Revenue"
        ],
        "summary": "月別収益サマリ / Monthly Revenue Summary",
        "description": "### 用途\n`revenue_monthly_summary` ビューから月次の売上集計を取得する。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RevenueMonthlyRow"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRevenueMonthlyList"
      }
    },
    "/v1/admin/revenue/transactions": {
      "get": {
        "tags": [
          "収益 / Revenue"
        ],
        "summary": "トランザクション一覧 / Transaction List",
        "description": "### 用途\n`revenue_transactions` の明細一覧を返す。Stripe / Apple / Google などのチャネル横断で\n1 行 = 1 取引として並ぶ。`app_users` と `subscription_plans` を LEFT JOIN する。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "channel",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "transaction_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RevenueTransactionWithRelations"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRevenueTransactionsList"
      },
      "post": {
        "tags": [
          "収益 / Revenue"
        ],
        "summary": "取引を手動追加 / Add Transaction Manually",
        "description": "### 用途\nStripe / Apple / Google などの自動連携で取り込めなかった取引を直接追加する。\n\n### 認証・認可\n`requireAdmin` 必須。`admin_activity_logs` に `revenue.create` として記録される。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "channel": {
                    "type": "string"
                  },
                  "transaction_type": {
                    "type": "string"
                  },
                  "amount_minor": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string",
                    "default": "JPY"
                  },
                  "external_id": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "plan_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  }
                },
                "required": [
                  "occurred_at",
                  "channel",
                  "transaction_type",
                  "amount_minor"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevenueTransaction"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRevenueTransactionsCreate"
      }
    },
    "/v1/admin/revenue/transactions/{id}": {
      "get": {
        "tags": [
          "収益 / Revenue"
        ],
        "summary": "トランザクション詳細 / Transaction Detail",
        "description": "### 用途\n1 件のトランザクションを ID で取得する。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevenueTransaction"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRevenueTransactionsGet"
      }
    },
    "/v1/admin/revenue/aggregate": {
      "get": {
        "tags": [
          "収益 / Revenue"
        ],
        "summary": "期間集計 / Period Aggregation",
        "description": "### 用途\n任意の期間で `revenue_transactions` の合計金額・件数・平均を返す軽量集計 API。\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "channel",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "集計",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total_amount_minor": {
                      "type": "integer"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "avg_amount_minor": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "total_amount_minor",
                    "count",
                    "avg_amount_minor"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminRevenueAggregateList"
      }
    },
    "/v1/admin/subscriptions": {
      "get": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "契約一覧 / Subscription List",
        "description": "### 用途\n`user_subscriptions` を横断的に一覧する。1 行 = 1 ユーザーの 1 サブスクリプション契約。\n外部ストア（Apple / Google / Stripe）の `external_subscription_id` も含めて返す。\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`status` / `plan_id` / `user_id` で絞り込み。並びは `started_at DESC`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "plan_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "payment_provider",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "pricing_variant",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "campaign_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUserSubscription"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsList"
      }
    },
    "/v1/admin/subscriptions/active-count": {
      "get": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "プラン別アクティブ契約数 / Active Subscriptions by Plan",
        "description": "### 用途\n`status='active'` の `user_subscriptions` をプラン別にカウントして返す。\nプランごとの現役課金者数を可視化する集計 API。\n\n### 挙動・制約\nGROUP BY `plan_id` のみ。`active` 以外（`past_due` など）はカウント対象外。",
        "responses": {
          "200": {
            "description": "プラン別集計",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "plan_id": {
                        "type": "string",
                        "format": "uuid",
                        "examples": [
                          "00000000-0000-0000-0000-000000000000"
                        ]
                      },
                      "count": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "plan_id",
                      "count"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsActiveCountList"
      }
    },
    "/v1/admin/subscriptions/variant-performance": {
      "get": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "価格テストアーム別パフォーマンス",
        "description": "### 用途\n`subscription_plans` の `variant_label` × `billing_period` 別に active/trial 件数を返す。\nPhase 1.8 の 4 アーム同時テスト中の中間レビュー用。",
        "responses": {
          "200": {
            "description": "アーム別集計",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "variant_label": {
                        "type": "string"
                      },
                      "billing_period": {
                        "type": "string"
                      },
                      "active_count": {
                        "type": "integer"
                      },
                      "trial_count": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "variant_label",
                      "billing_period",
                      "active_count",
                      "trial_count"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsVariantPerformanceList"
      }
    },
    "/v1/admin/subscriptions/plans": {
      "get": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "購読プラン一覧（管理）/ Subscription Plan List (Admin)",
        "description": "### 用途\n`subscription_plans` の管理用一覧。公開 API と違い、`is_active=false` の停止済みプランや\n内部メモ・並び順・カラーなど運用に必要なフィールドも含めて返す。\n\n### 挙動・制約\n`is_active` （`true`/`false` 文字列）と `billing_period` で絞り込み可。\n並びは `sort_order ASC`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "billing_period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "plan_group",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "variant_label",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_test_variant",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminSubscriptionPlan"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsPlansList"
      },
      "post": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "プラン新規作成 (variant 追加)",
        "description": "### 用途\n価格テスト variant や新プラン (plan_group=premium_basic, variant_label=arm_x など) の追加。\n\n### 認証・認可\n`requireAdmin` + `revenue:write` 必須。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminSubscriptionPlanCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "作成",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSubscriptionPlan"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsPlansCreate"
      }
    },
    "/v1/admin/subscriptions/plans/by-code/{code}": {
      "get": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "プラン詳細 / Plan Detail",
        "description": "### 用途\nUUID ではなく plan code（`subscription_plans.code`、UNIQUE）でプラン 1 件を取得する。\n\n### 挙動・制約\n存在しない code は 404 `not_found`。`is_active=false` のプランも取得可（公開 API と異なる管理者権限）。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSubscriptionPlan"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsPlansByCodeGet"
      },
      "patch": {
        "tags": [
          "サブスクリプション / Subscriptions"
        ],
        "summary": "プランを更新 / Update Plan",
        "description": "### 用途\nplan code でプラン定義を部分更新する。`code` 自体と PK / 監査用列は変更不可。\n`features` は文字列配列（DB 側 jsonb）として保存される。\n\n### 更新可能フィールド\n- `name` / `description` / `price_minor` / `currency` / `billing_period`\n- `accent_color` / `features` / `sort_order` / `is_active`\n\n### 挙動・制約\n空ボディは現状値を 200 で返す（idempotent）。`features` を渡すと配列全体置換。\n監査ログ（`subscription_plan.update`, critical）を記録。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "price_minor": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "billing_period": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "accent_color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "features": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 50
                    }
                  },
                  "sort_order": {
                    "type": "integer"
                  },
                  "is_active": {
                    "type": "boolean"
                  },
                  "plan_group": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "variant_label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "trial_days": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "discount_percent": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 99
                  },
                  "is_test_variant": {
                    "type": "boolean"
                  },
                  "platform_product_ids": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminSubscriptionPlan"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSubscriptionsPlansByCodeUpdate"
      }
    },
    "/v1/admin/pricing-campaigns": {
      "get": {
        "tags": [
          "pricing-campaigns"
        ],
        "summary": "価格キャンペーン一覧",
        "description": "### 用途\n`pricing_campaigns` の管理用一覧。期間限定の Premium 特別単価キャンペーンを返す。\n\n### 認証・認可\n`requireAdmin` + `revenue:read` 必須。\n\n### 挙動・制約\n`status` / `is_active` / `plan_group` / `q` (name/code 部分一致) / `active_now` で絞り込み。\n並びは `starts_at DESC`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "plan_group",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active_now",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminPricingCampaign"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPricingCampaignsList"
      },
      "post": {
        "tags": [
          "pricing-campaigns"
        ],
        "summary": "キャンペーンを作成 / Create Campaign",
        "description": "### 用途\n新しい価格キャンペーンを作成する。`code` (UNIQUE) でキャンペーン識別。\n\n### 必須 / 任意\n`override_price_minor` または `override_discount_percent` のどちらかを必ず指定。\n両方指定はエラー。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminPricingCampaignCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "作成",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminPricingCampaign"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPricingCampaignsCreate"
      }
    },
    "/v1/admin/pricing-campaigns/by-code/{code}": {
      "get": {
        "tags": [
          "pricing-campaigns"
        ],
        "summary": "キャンペーン詳細 (natural key: code)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminPricingCampaign"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPricingCampaignsByCodeGet"
      },
      "patch": {
        "tags": [
          "pricing-campaigns"
        ],
        "summary": "キャンペーン更新 (natural key: code)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminPricingCampaignUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminPricingCampaign"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPricingCampaignsByCodeUpdate"
      },
      "delete": {
        "tags": [
          "pricing-campaigns"
        ],
        "summary": "キャンペーンを削除 / Delete Campaign",
        "description": "適用済 (current_redemptions > 0) のキャンペーンも削除可能だが、`is_active=false` で停止する運用を推奨。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除完了"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPricingCampaignsByCodeDelete"
      }
    },
    "/v1/admin/membership-dashboard": {
      "get": {
        "tags": [
          "membership-dashboard"
        ],
        "summary": "メンバーシップダッシュボード",
        "description": "### 用途\nFree/Premium 会員数、価格テストアーム別パフォーマンス、トライアルファネルを 1 リクエストで返す。\n管理画面の「メンバーシップ」ダッシュボードで使用。\n\n### 認証・認可\n`requireAdmin` + `revenue:read` 必須。",
        "responses": {
          "200": {
            "description": "ダッシュボード",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminMembershipDashboard"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminMembershipDashboardList"
      }
    },
    "/v1/admin/store-integrations": {
      "get": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "ストア連携設定一覧 / Store Integration List",
        "description": "### 用途\nGoogle Play Console / App Store Connect 等の外部ストア連携設定 (`store_integrations`) を一覧する。\n接続状態・最終同期時刻・直近のエラーメッセージを返し、運用ヘルスチェックに使う。\n\n### 認証・認可\n`requireAdmin` 必須。サービスアカウントキー等の機密値は返さず、状態と表示用情報のみを返す。\n\n### 挙動・制約\n並びは `store ASC`。`connection_status` は `not_configured` / `connected` / `error` のコード値。",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/StoreIntegration"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreIntegrationsList"
      }
    },
    "/v1/admin/store-integrations/by-store/{store}": {
      "get": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "ストア連携設定詳細 / Store Integration Detail",
        "description": "### 用途\nストア種別コード（`play_store` / `app_store`、DB CHECK と完全一致）で連携設定 1 件を取得する。\n\n### 挙動・制約\n見つからない場合は 404 `not_found`（初期 seed 済なので通常は起きない）。",
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/StorePlatform"
            },
            "required": true,
            "name": "store",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoreIntegration"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreIntegrationsByStoreGet"
      },
      "patch": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "ストア連携設定を更新 / Update Store Integration",
        "description": "### 用途\nストア種別コードでストア連携設定を部分更新する。`store` カラム自体と PK / 監査用列は変更不可。\n\n### 更新可能フィールド\n- `display_name` / `package_or_app_id` / `vendor_number`\n- `connection_status` / `last_status_message` / `last_synced_at` / `last_sync_error`\n\n### 挙動・制約\n空ボディ送信は現状値を 200 で返す（idempotent）。WHERE 句では store ではなく id 解決後にマッチする。",
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/StorePlatform"
            },
            "required": true,
            "name": "store",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "display_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "package_or_app_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "vendor_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50
                  },
                  "connection_status": {
                    "$ref": "#/components/schemas/StoreConnectionStatus"
                  },
                  "last_status_message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "last_synced_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "last_sync_error": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoreIntegration"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreIntegrationsByStoreUpdate"
      }
    },
    "/v1/admin/store-sales": {
      "get": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "ストア売上日報 / Store Sales Report",
        "description": "### 用途\nPlay Store / App Store から取り込んだ日次売上 (`store_sales_daily`) を一覧する。\n1 行 = （ストア × 日付 × 商品 × 国） で `units` / `proceeds_minor` / `gross_minor` を持つ。\n\n### 挙動・制約\n`store` (`play_store` / `app_store`) と `from` / `to`（report_date 範囲）で絞り込み。\n並びは `report_date DESC`。データの更新は同期ジョブ依存で、Apple は数日遅れ、Google は最大 1 日遅れ。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "play_store",
                "app_store"
              ]
            },
            "required": false,
            "name": "store",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StoreSalesRow"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreSalesList"
      }
    },
    "/v1/admin/store-metrics": {
      "get": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "アプリ指標日報 / App Metrics Report",
        "description": "### 用途\nストア指標日報 (`store_app_metrics_daily`) を一覧する。\nインストール数・アクティブ端末数・ストア露出（impressions / product_page_views）・\nクラッシュ率・ANR 率・評価平均など、運営が日次で追う KPI をストア API から取り込んだもの。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > 店舗連携 > アプリ指標 タブの折れ線グラフ\n- ダッシュボードの「インストール推移」「クラッシュ率」KPI\n- リリース後の品質モニタリング（クラッシュ率急上昇のアラート対象）\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`store` / `from` / `to`（metric_date 範囲）で絞り込み。並びは `metric_date DESC`。\nストア API の都合で前日分が翌々日にしか入らない指標もあるため、欠落日が出ることがある。\n\n### 関連\n- `GET /v1/admin/store-reviews` — レビュー一覧\n- `POST /v1/admin/store-sync/trigger` — `task=metrics` で再取得",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "play_store",
                "app_store"
              ]
            },
            "required": false,
            "name": "store",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StoreMetricsRow"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreMetricsList"
      }
    },
    "/v1/admin/store-reviews": {
      "get": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "ストアレビュー一覧 / Store Review List",
        "description": "### 用途\nPlay Store / App Store のユーザーレビュー (`store_reviews`) を一覧する。\n返信済み (`reply_at`)・未返信を切り替えられ、CS チームが対応漏れを潰すための画面で使う。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > 店舗連携 > レビュー管理 タブ\n- 「未返信レビュー件数」のヘッダーバッジを更新するとき（`unreplied=true` で count）\n- 低評価レビューのトレンド分析（評価別に表示）\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`store` / `unreplied=true` で絞り込み。並びは `posted_at DESC`。\nレビュー本体・返信本文・投稿者表示名はストアから取得した文字列をそのまま保持する。\n返信投稿はこの API では行わず、別途同期ジョブ or 専用 API で行う想定。\n\n### 関連\n- `POST /v1/admin/store-sync/trigger` — `task=reviews` で再取得\n- `GET /v1/admin/store-app-metrics` — 評価平均などサマリ指標",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "play_store",
                "app_store"
              ]
            },
            "required": false,
            "name": "store",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "unreplied",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StoreReview"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreReviewsList"
      }
    },
    "/v1/admin/store-sync-runs": {
      "get": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "同期ジョブ履歴 / Sync Job History",
        "description": "### 用途\nストア同期ジョブの実行履歴 (`store_sync_runs`) を一覧する。\n1 行 = 1 回の `(store, task)` 実行で、`status`\n(`queued` / `started` / `success` / `partial` / `error` / `skipped_not_configured`) と\n取り込み件数 (`rows_upserted`) を持つ。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > 店舗連携 > 同期履歴 タブ\n- 「同期実行」ボタン押下後の進行状況ポーリング\n- 失敗ジョブの message 列を見て原因調査するとき\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n並びは `started_at DESC`。フィルタは現状ページングのみ。\n`triggered_by` は手動起動時の `admins.id` または cron の場合は固定値。\nQueue から Worker 側で実体処理 → `status` を遷移させていく設計。\n\n### 関連\n- `POST /v1/admin/store-sync/trigger` — 新規ジョブ投入\n- `GET /v1/admin/store-integrations` — 接続設定の状態確認",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StoreSyncRun"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreSyncRunsList"
      }
    },
    "/v1/admin/store-sync-trigger": {
      "post": {
        "tags": [
          "ストア連携 / Store Integrations"
        ],
        "summary": "ストア同期を手動起動 / Trigger Store Sync",
        "description": "### 用途\nストア同期ジョブを手動起動する。`store` × `task` の直積分だけ `store_sync_runs` を\n`status='queued'` で INSERT し、各 run_id を `parky-store-sync` Cloudflare Queue に投入する。\n実体の取り込みは Worker（コンシューマ）側で非同期に実行される。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > 店舗連携 トップの「今すぐ同期」ボタン\n- 同期失敗ジョブのリトライ操作\n- リリース直後など、定時バッチを待たずに最新データを取り込みたいとき\n\n### 認証・認可\n`requireAdmin` 必須。`admin_activity_logs` に `store_sync.trigger` として記録される。\n\n### 挙動・制約\n`store=all` で `[play_store, app_store]`、`task=all` で `[sales, metrics, reviews]` に展開。\nQueue 投入は (store, task) ごとに 1 メッセージ（`StoreSyncMessage`）。レスポンスは 202 で\n投入済みの `sync_run_ids` を返す。実際の進行は `GET /v1/admin/store-sync-runs` をポーリング。\nQueue 投入失敗時は INSERT 済み行が `queued` のまま残るため、別途リカバリ処理が必要。\n\n### 関連\n- `GET /v1/admin/store-sync-runs` — ジョブ進行ポーリング\n- `GET /v1/admin/store-integrations` — 連携設定の事前確認",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "store": {
                    "type": "string",
                    "enum": [
                      "play_store",
                      "app_store",
                      "all"
                    ],
                    "default": "all"
                  },
                  "task": {
                    "type": "string",
                    "enum": [
                      "sales",
                      "metrics",
                      "reviews",
                      "all"
                    ],
                    "default": "all"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "同期ジョブが開始されました",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sync_run_ids": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid",
                        "examples": [
                          "00000000-0000-0000-0000-000000000000"
                        ]
                      }
                    },
                    "count": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued"
                      ]
                    }
                  },
                  "required": [
                    "sync_run_ids",
                    "count",
                    "status"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "bad_gateway",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminStoreSyncTriggerCreate"
      }
    },
    "/v1/admin/badges": {
      "get": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジ定義一覧 / Badge Definition List",
        "description": "### 用途\nゲーミフィケーションのバッジ定義（`badge_definitions`）一覧をページング付きで返す。\n`category` / `activity_type` / `is_active` でフィルタでき、各バッジの取得者数・タグ・\nアイコンアセット情報も同梱して返す。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。管理者でなければ 401/403。\n\n### 挙動・制約\n- `holders_count` は `user_badges` の相関サブクエリで都度集計（バッジ獲得者数）\n- `tags` は `badge_definition_tags` を `array_agg` で配列化\n- `asset` は LEFT JOIN `assets` の結果（アイコン画像メタ）\n- 並び順は `sort_order ASC` 固定\n\n### 関連\n- `POST /v1/admin/badges/` — バッジ定義作成\n- `PATCH /v1/admin/badges/{badge_code}` — バッジ定義更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "activity_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BadgeDefinition"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesList"
      },
      "post": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジ定義を作成 / Create Badge Definition",
        "description": "### 用途\n新しいバッジ定義を作成する。バッジは `activity_type` と `threshold` を組み合わせ、\nユーザーがある活動を一定回数達成したときに自動付与される。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 関連\n- `PATCH /v1/admin/badges/{badge_code}` — 作成後の更新\n- `PUT /v1/admin/badges/{badge_code}/tags` — タグ付与",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BadgeDefinitionUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadgeDefinition"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesCreate"
      }
    },
    "/v1/admin/badges/{badge_code}": {
      "patch": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジ定義を更新（natural key = badge_definitions.badge_code）",
        "description": "### 用途\n既存のバッジ定義を部分更新する。空ボディを送ると現在値を返す（pre-fetch ヘルパとして使える）。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- path param `{badge_code}` は `badge_definitions.badge_code` (natural key, UNIQUE) を指定\n- ボディが空オブジェクトのときは UPDATE せず現在値を返す\n- 更新差分は `admin_activity_logs.metadata.diff` にフルで保存（監査用）\n\n### 関連\n- `PUT /v1/admin/badges/{badge_code}/tags` — タグの差し替え",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "badge_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BadgeDefinitionUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadgeDefinition"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesUpdate"
      }
    },
    "/v1/admin/badges/{badge_code}/tags": {
      "put": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジのタグを一括置き換え（natural key = badge_code）",
        "description": "### 用途\nバッジ定義に紐づくタグ（`badge_definition_tags`）を**完全に置き換える**。\n送ったタグ配列がそのまま新しい状態となる（差分マージではない）。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- path param `{badge_code}` は `badge_definitions.badge_code` (natural key, UNIQUE)\n- トランザクション内で `DELETE → INSERT` のフル差し替え\n- 空配列を送るとタグなしになる\n\n### 関連\n- `GET /v1/admin/badges/` — タグは一覧 API のレスポンスに含まれる",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "badge_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "tags"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesTagsReplace"
      }
    },
    "/v1/admin/badges/{badge_code}/progress-summary": {
      "get": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジ取得状況の集計 / Badge Progress Summary",
        "description": "### 用途\n指定バッジの進捗状況を 3 つの数値で返す: 進捗を持つ総ユーザー数（`total`）、\n獲得済みユーザー数（`awarded`）、進行中ユーザー数（`in_progress`）。\n\n### 管理者ポータルでの使用タイミング\n- バッジ詳細画面のサマリーカード表示\n- 「このバッジは何人取った？」を即座に確認したいとき\n- 閾値変更後の達成度を確認するとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `user_badge_progress` を `COUNT(*) FILTER (WHERE awarded = ...)` で 1 クエリ集計\n- progress テーブル未生成のユーザーはカウントされない（`POST /backfill` で生成）\n\n### 関連\n- `GET /v1/admin/badges/{badge_code}/progress-users` — 個別ユーザー一覧\n- `POST /v1/admin/badges/{badge_code}/backfill` — 進捗を再計算",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "badge_code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "集計",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "badge_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    },
                    "total": {
                      "type": "integer"
                    },
                    "awarded": {
                      "type": "integer"
                    },
                    "in_progress": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "badge_id",
                    "total",
                    "awarded",
                    "in_progress"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesProgressSummaryList"
      }
    },
    "/v1/admin/badges/{badge_code}/progress-users": {
      "get": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジ進捗ユーザー一覧 / Badge Progress Users",
        "description": "### 用途\n指定バッジの進捗を持つユーザーを最大 500 件まで返す。獲得済み → 進捗大の順で並ぶので、\n上位達成者の確認や個別調整に使う。\n\n### 管理者ポータルでの使用タイミング\n- バッジ詳細画面の「取得者・進捗ランキング」表示\n- 進捗が異常な値になっているユーザーの調査\n- カスタマー対応で「このバッジを持っているか確認したい」とき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `user_badge_progress` LEFT JOIN `app_users` で `display_name` も付与\n- 並び順: `awarded DESC, progress DESC NULLS LAST`\n- `limit` 省略時は 100 件、最大 500 件\n\n### 関連\n- `GET /v1/admin/badges/{badge_code}/progress-summary` — 全体集計",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "badge_code",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "badge_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "progress": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "awarded": {
                            "type": "boolean"
                          },
                          "awarded_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "display_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "metadata": {
                            "type": "object",
                            "additionalProperties": {}
                          }
                        },
                        "required": [
                          "badge_id",
                          "user_id",
                          "progress",
                          "awarded",
                          "awarded_at",
                          "display_name"
                        ]
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesProgressUsersList"
      }
    },
    "/v1/admin/badges/{badge_code}/backfill": {
      "post": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "バッジ進捗を再計算 / Recalculate Badge Progress",
        "description": "### 用途\n指定バッジの全ユーザー進捗を `user_activity_logs` から再計算する。\n閾値や `activity_type` を後から変えた場合や、何らかの不整合が生じた場合の修復用。\n\n### 管理者ポータルでの使用タイミング\n- バッジ定義の `threshold` を変更した直後\n- 進捗カウントが期待値とズレているとの問い合わせ対応\n- 過去ログから新規バッジを後追い付与するとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- Postgres 関数 `backfill_badge_progress(target_badge_id)` を呼び出す\n- 実行時間は対象ユーザー数に比例（数千件規模を想定）\n- `admin_activity_logs` に `badge.backfill` を記録\n\n### 関連\n- `POST /v1/admin/badges/backfill-all` — 全バッジ一括",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "badge_code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "再計算結果 (RPC 戻り値をそのまま返す)",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesBackfillCreate"
      }
    },
    "/v1/admin/badges/backfill-all": {
      "post": {
        "tags": [
          "バッジ / Badges"
        ],
        "summary": "全バッジ進捗を再計算 / Recalculate All Badge Progress",
        "description": "### 用途\n全バッジ × 全ユーザーの進捗を一括で再計算する。新環境セットアップやスキーマ変更後の\n整合性復旧に使う。\n\n### 管理者ポータルでの使用タイミング\n- ゲーミフィケーション設定タブの「全バッジ進捗を再計算」ボタン\n- マイグレーション直後の整合性チェック後\n- 大規模なバッジ定義変更（複数バッジを同時改訂）後\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- Postgres 関数 `backfill_all_badge_progress()` を呼び出す\n- 全件走査のため重い。利用頻度の少ない時間帯に実行を推奨\n- `admin_activity_logs` に `badge.backfill_all` を記録\n\n### 関連\n- `POST /v1/admin/badges/{badge_code}/backfill` — 個別バッジ向け",
        "responses": {
          "200": {
            "description": "結果 (RPC 戻り値をそのまま返す)",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminBadgesBackfillAllCreate"
      }
    },
    "/v1/admin/exp-rules": {
      "get": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "EXPルール一覧 / EXP Rule List",
        "description": "### 用途\nアクティビティ別 EXP 付与ルール（`activity_exp_rules`）の全件を返す。\n「駐車1回 = 50 EXP」のような付与テーブル。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > ゲーミフィケーション管理 > EXP ルールタブを開いたとき\n- 新規ルール追加前に既存設定を確認するとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 並び順: `activity_type ASC`\n- ページングなしで全件取得（件数は数十件程度を想定）\n- `is_active=false` のルールも含む\n\n### 関連\n- `POST /v1/admin/exp-rules/` — 新規作成\n- `PATCH /v1/admin/exp-rules/{id}` — 更新",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ActivityExpRule"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminExpRulesList"
      },
      "post": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "EXPルールを作成 / Create EXP Rule",
        "description": "### 用途\n新しいアクティビティタイプの EXP 付与ルールを追加する。`activity_type` をキーに\nユーザーアクティビティ発生時の付与 EXP を決定する。\n\n### 管理者ポータルでの使用タイミング\n- EXP ルールタブで「新規ルール」ボタン押下時\n- 新しいユーザー行動タイプ（例: シェア、招待）を導入する際\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `exp_amount` は 0 以上の整数\n- `is_active` 既定 `true`、`description` 既定空文字\n- 同一 `activity_type` 重複は DB 制約で防がれる場合は 409 を返す\n- `admin_activity_logs` に `exp_rule.create` を記録\n\n### 関連\n- `PATCH /v1/admin/exp-rules/{id}` — 後から金額調整",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "activity_type": {
                    "type": "string"
                  },
                  "exp_amount": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "description": {
                    "type": "string",
                    "default": ""
                  },
                  "is_active": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "activity_type",
                  "exp_amount"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityExpRule"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminExpRulesCreate"
      }
    },
    "/v1/admin/exp-rules/{id}": {
      "patch": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "EXPルールを更新 / Update EXP Rule",
        "description": "### 用途\n既存 EXP ルールを部分更新する。EXP 量の調整や有効/無効の切り替えに使う。\n\n### 管理者ポータルでの使用タイミング\n- EXP ルール一覧の編集ダイアログで保存ボタン押下時\n- キャンペーン期間中の EXP ボーナス（一時増額）\n- 不要になったルールの非アクティブ化\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 空ボディは現在値返却\n- 該当 ID なしは 404 `not_found`\n- `admin_activity_logs.metadata.diff` に変更内容を保存\n\n### 関連\n- `DELETE /v1/admin/exp-rules/{id}` — 完全削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminExpRuleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityExpRule"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminExpRulesUpdate"
      },
      "delete": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "EXPルールを削除 / Delete EXP Rule",
        "description": "### 用途\nEXP ルールをハード削除する。論理削除ではなく物理削除する点に注意。\n保存しておきたい場合は `is_active=false` で運用する方が安全。\n\n### 管理者ポータルでの使用タイミング\n- EXP ルール一覧でゴミ箱アイコン押下時\n- 設計ミスで誤って作成したルールの除去\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `DELETE FROM activity_exp_rules WHERE id = $1` の単純削除\n- 過去の付与履歴は `user_exp_logs` 等に残る（このテーブルには影響しない）\n- `admin_activity_logs` に `exp_rule.delete` を記録\n\n### 関連\n- `PATCH /v1/admin/exp-rules/{id}` — 削除の代替（is_active=false）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminExpRulesDelete"
      }
    },
    "/v1/admin/level-definitions": {
      "get": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "レベル定義一覧 / Level Definition List",
        "description": "### 用途\nレベル定義（`level_definitions`）の全件をレベル昇順で返す。\n「Lv N に到達するのに必要な累計 EXP」のテーブル。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > ゲーミフィケーション管理 > レベル定義タブを開いたとき\n- 一括 upsert（PUT）前の現在値を確認するとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 並び順: `level ASC`\n- ページングなしで全件返却（最大数十件想定）\n\n### 関連\n- `PUT /v1/admin/levels/` — レベル定義一括 upsert\n- `POST /v1/admin/user-levels/recalculate` — 全ユーザーレベル再計算",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LevelDefinition"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminLevelDefinitionsList"
      },
      "put": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "レベル定義を一括更新 / Bulk Upsert Level Definitions",
        "description": "### 用途\nレベル定義テーブルを送信されたデータで**全置換**する。レベルカーブを丸ごと差し替える\nイメージ。差分更新ではないため、既存値を残したい場合は API 呼出前にマージしておく。\n\n### 管理者ポータルでの使用タイミング\n- レベル定義タブで「保存」ボタンを押下した時\n- 新仕様のレベルカーブをローンチする際\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- トランザクションで `DELETE → INSERT` を実行（途中失敗時はロールバック）\n- 後続のユーザーレベル整合性は `POST /v1/admin/user-levels/recalculate` で別途回す必要あり\n- `admin_activity_logs` に `level.bulk_update` と件数を記録\n\n### 関連\n- `POST /v1/admin/user-levels/recalculate` — ユーザーレベル再計算",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "level": {
                          "type": "integer",
                          "minimum": 1
                        },
                        "required_exp": {
                          "type": "integer",
                          "minimum": 0
                        }
                      },
                      "required": [
                        "level",
                        "required_exp"
                      ]
                    }
                  }
                },
                "required": [
                  "rows"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminLevelDefinitionsReplace"
      }
    },
    "/v1/admin/level-definitions/user-counts": {
      "get": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "レベル別のユーザー数",
        "description": "### 用途\n`user_exp.level` で GROUP BY したユーザー数を `level ASC` で返す。\n管理者ポータルのレベル定義一覧で、各レベルに何人のユーザーが在籍しているかを表示するために使う。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > ゲーミフィケーション管理 > レベル定義 一覧の各行右端「ユーザー数」列\n- 「レベル付きユーザー数」サマリーカード\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `user_exp` 行が無いレベルは結果に含まれない（クライアント側で 0 補完を想定）\n- 並び順: `level ASC`\n\n### 関連\n- `GET /v1/admin/level-definitions/{level}/users` — 特定レベルのユーザー一覧",
        "responses": {
          "200": {
            "description": "レベル別ユーザー数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "counts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "level": {
                            "type": "integer"
                          },
                          "count": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "level",
                          "count"
                        ]
                      }
                    }
                  },
                  "required": [
                    "counts"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminLevelDefinitionsUserCountsList"
      }
    },
    "/v1/admin/level-definitions/{level}/users": {
      "get": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "特定レベルのユーザー一覧",
        "description": "### 用途\n指定レベルに到達しているユーザー（`user_exp.level = {level}`）を `app_users.display_name` 付きで返す。\n\n### 管理者ポータルでの使用タイミング\n- LevelsPage で「ユーザー数」セルをクリックして開く一覧モーダル\n- 高位レベル達成者の確認 / EXP 補正対応の事前確認\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 並び順: `total_exp DESC`（同一レベル内で進捗が大きい順）\n- `limit` 省略時は 100 件、最大 500 件\n- 該当ユーザー無しでも 200 + 空配列\n\n### 関連\n- `GET /v1/admin/level-definitions/user-counts` — レベル別の総数\n- `GET /v1/admin/users/{id}` — ユーザー詳細",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": true,
            "name": "level",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "user_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "total_exp": {
                            "type": "integer"
                          },
                          "level": {
                            "type": "integer"
                          },
                          "updated_at": {
                            "type": "string"
                          },
                          "display_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "user_id",
                          "total_exp",
                          "level",
                          "updated_at",
                          "display_name"
                        ]
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminLevelDefinitionsUsersList"
      }
    },
    "/v1/admin/level-definitions/{level}": {
      "delete": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "単一レベル定義の削除",
        "description": "### 用途\n指定レベル番号をキーとして `level_definitions` 行を 1 件削除する。\n通常はレベルカーブ全体を扱うため `PUT /` の bulk upsert を使うが、\n末尾レベルだけ削りたいケースのために存在する。\n\n### 管理者ポータルでの使用タイミング\n- LevelsPage で末尾レベルの「削除」ボタン押下\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 削除対象が無ければ 404 を返す\n- 削除後はユーザーレベル整合性のため `POST /v1/admin/user-levels/recalculate` 推奨\n- `admin_activity_logs` に `level.delete` を記録\n\n### 関連\n- `PUT /v1/admin/level-definitions` — 一括 upsert（差し替え）\n- `POST /v1/admin/user-levels/recalculate` — 全ユーザーレベル再計算",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "required": true,
            "name": "level",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "削除結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "deleted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminLevelDefinitionsDelete"
      }
    },
    "/v1/admin/user-levels/recalculate": {
      "post": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "全ユーザーレベルを再計算 / Recalculate All User Levels",
        "description": "### 用途\n全ユーザーの累計 EXP と最新の `level_definitions` を突き合わせ、ユーザーごとの\nレベル（`user_levels` 等）を最新化する。レベルカーブを変更した直後に必ず実行する。\n\n### 管理者ポータルでの使用タイミング\n- レベル定義の `PUT` 一括 upsert を保存した直後\n- EXP ルール改訂後の整合性復旧\n- ユーザーから「レベル表示がおかしい」と問い合わせがあったとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- Postgres 関数 `recalculate_all_user_levels()` を呼び出す\n- 全ユーザーを走査するため重い。実行は低トラフィック帯を推奨\n- `admin_activity_logs` に `user_level.recalculate` を記録\n\n### 関連\n- `PUT /v1/admin/levels/` — このエンドポイントの前段で実行する",
        "responses": {
          "200": {
            "description": "結果 (RPC 戻り値をそのまま返す)",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserLevelsRecalculateCreate"
      }
    },
    "/v1/admin/gamification/anomalies": {
      "get": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "EXP付与異常検知 / EXP Anomaly Detection",
        "description": "ゲーミフィケーション異常検知用の 4 系統集計を 1 レスポンスで返す read-only エンドポイント。\n\n### 返す内容\n- `daily_cap_hits`: 直近 48h、hour_bucket × activity_type の daily_cap ヒット件数\n- `geo_unverified`: 直近 48h、geo_verified=false で EXP 付与しなかった件数\n- `top_exp_users_24h`: 直近 24h に 100 EXP 以上獲得したユーザー（降順 TOP 100）\n- `pending_referrals`: 直近 30 日の紹介適用状況（pending / confirmed / rejected）\n\n### 用途\nadmin ポータルで EXP 付与異常の可視化 / abuse 兆候の早期発見。\nview は public schema だが authenticated からは REVOKE 済み、service_role のみ SELECT 可。",
        "responses": {
          "200": {
            "description": "4 系統の集計結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamificationAnomalies"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminGamificationAnomaliesList"
      }
    },
    "/v1/admin/gamification/anomalies/pending-referrals": {
      "get": {
        "tags": [
          "ゲーミフィケーション / Gamification"
        ],
        "summary": "紹介適用 pending 一覧 / Pending Referral Applications",
        "description": "### 用途\n`public.gamification_pending_referrals` view を cursor ベースで全件走査可能にする。\n`GET /` 集計エンドポイントは 200 件で cap されるため、監査や abuse 調査で全件を\n辿りたいときはこちらを使う。\n\n### 並び\n`applied_at DESC, id DESC` の keyset。\n\n### cursor の使い方\n1. 初回は `cursor` パラメータを省略。\n2. レスポンスの `next_cursor` が `null` でなければ、次のリクエストに `cursor=<value>` を渡す。\n3. `has_more=false` になったら終端。\n\n### フィルタ\n- `status`: 紹介適用のステータス（confirmed / rejected / pending）\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "confirmed",
                "rejected",
                "pending"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "紹介適用 pending 一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "referrer_user_id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "referrer_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "referee_user_id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "referee_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "referee_signup_at": {
                            "type": "string"
                          },
                          "applied_at": {
                            "type": "string"
                          },
                          "referee_device_fingerprint": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "referee_lifetime_amount_minor": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "confirmed",
                              "rejected",
                              "pending"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "referrer_user_id",
                          "referrer_name",
                          "referee_user_id",
                          "referee_name",
                          "referee_signup_at",
                          "applied_at",
                          "referee_device_fingerprint",
                          "referee_lifetime_amount_minor",
                          "status"
                        ]
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminGamificationAnomaliesPendingReferralsList"
      }
    },
    "/v1/admin/themes": {
      "get": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマ一覧 / Theme List",
        "description": "### 用途\nゲーミフィケーション用の「テーマ（スキン）」商品一覧を返す。`customization_themes` 本体に\n加え、プレビュー画像（`assets`）・所有ユーザー数（post-fetch aggregation）・スロット\n割当（`customization_theme_items`）を 1 リクエストで返却し、商品管理画面で必要な情報を\nまとめて取得できるようにしている。\n\n### 管理者ポータルでの使用タイミング\n- 運営 > ゲーミフィケーション管理 > テーマ一覧\n- 公開 / 非公開タブ切替（`is_active`）\n- 無料 / 有料タブ切替（`is_free`）\n- ショップ商品の在庫確認 / 並び順チェック\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- ページング: `PageQuerySchema`\n- 並び順: `sort_order ASC`（ショップ陳列順）\n- `items` は別クエリで `theme_id IN (...)` 一括取得し N+1 回避\n- `owners_count` は post-fetch GROUP BY で計算（N+1 回避）\n\n### 関連\n- `POST /v1/admin/themes` — テーマ作成\n- `PUT /v1/admin/themes/{theme_code}/items` — スロット割当\n- `POST /v1/admin/themes/{theme_code}/gift` — プレゼント配布",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_free",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminTheme"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemesList"
      },
      "post": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマを作成 / Create Theme",
        "description": "### 用途\n新規テーマ（スキン商品）を `customization_themes` に登録する。名前 / 説明 / 無料フラグ /\n価格 / プレビュー画像 ID / 公開フラグ / 並び順を含むレコードを作る。\n\n### 管理者ポータルでの使用タイミング\n- ゲーミフィケーション管理 > テーマ > 「新規テーマ」ボタン\n- イベント連動の限定テーマ投入\n- 既存テーマの複製 → 派生バリエーション作成\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- ボディ `passthrough`\n- プレビュー画像は assets で先にアップロードして `preview_asset_id` を渡す\n- スロット割当（pin / icon 等）は別 API `PUT /{id}/items` で後付け\n- 監査ログ `theme.create` に is_free / price_yen_minor を記録\n- 作成直後は `is_active=false` で下書き的に扱い、揃ってから公開する運用を想定\n\n### 関連\n- `PUT /v1/admin/themes/{theme_code}/items` — スロット割当\n- `PATCH /v1/admin/themes/{theme_code}` — 公開フラグ変更",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminThemeUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminTheme"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemesCreate"
      }
    },
    "/v1/admin/themes/{theme_code}": {
      "patch": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマを更新（natural key = customization_themes.theme_code）",
        "description": "### 用途\nテーマ本体の属性（名前 / 説明 / 価格 / 公開フラグ / 並び順 等）を部分更新する。\nスロット割当は別 API。空ボディは現行値返却。\n\n### 管理者ポータルでの使用タイミング\n- テーマ編集画面「保存」\n- 公開フラグ（`is_active`）のインライン切替\n- 並び順（`sort_order`）のドラッグ&ドロップ確定\n- 価格・無料フラグ変更\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- 該当 ID なしは `not_found`（404）\n- 監査ログ `theme.update` に diff を記録\n- スロット割当の変更には `PUT /{id}/items` を別途使うこと\n\n### 関連\n- `DELETE /v1/admin/themes/{theme_code}` — 物理削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "theme_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminThemeUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminTheme"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemesUpdate"
      },
      "delete": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマを削除（natural key = theme_code）",
        "description": "### 用途\nテーマを物理削除する（`DELETE FROM customization_themes`）。所有ユーザーがいる場合は\nFK 制約で失敗するため、原則として未配布のテーマのみ削除可。配布済みは `is_active=false` で\n陳列を止める運用が安全。\n\n### 管理者ポータルでの使用タイミング\n- テーマ編集画面「削除」確認モーダル承認後\n- 公開前のテスト用テーマのクリーンアップ\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- 物理削除（DELETE）\n- `customization_theme_items` の FK ON DELETE CASCADE 有無は DB スキーマ依存\n- 監査ログ `theme.delete` に削除前の name を残す\n- 存在しない ID でも 204（冪等）\n\n### 関連\n- `PATCH /v1/admin/themes/{theme_code}` — 非公開化（履歴を残す場合）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "theme_code",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemesDelete"
      }
    },
    "/v1/admin/themes/{theme_code}/items": {
      "put": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマのスロット割当を一括置換 / Replace Theme Slot Assignments",
        "description": "### 用途\nテーマに紐付くスロット割当（`customization_theme_items`）を **一括置換** する。\n1 テーマには「pin」「icon」「loading」など複数カテゴリのパーツが紐付き、`category → part_id`\nの組を配列で渡す。既存割当はトランザクション内で全削除 → 全 INSERT する。\n\n### 管理者ポータルでの使用タイミング\n- テーマ編集画面のスロット割当タブで「保存」\n- ドラッグ&ドロップでパーツを差し替えた後の確定\n- 空配列を送ってスロットをクリア\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- DELETE → INSERT を 1 トランザクションで実行（中途半端な状態にならない）\n- `items: []` を送ると現行スロットを全削除\n- 同じ category が複数あった場合の扱いはユニーク制約に従う（DB 側で一意化）\n- 監査ログ `theme.update_items` に件数を記録\n\n### 関連\n- `GET /v1/admin/themes` — `items` がレスポンスに含まれる\n- `GET /v1/admin/theme-parts` — 割当対象パーツの選択肢",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "theme_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "category": {
                          "type": "string"
                        },
                        "part_id": {
                          "type": "string",
                          "format": "uuid",
                          "examples": [
                            "00000000-0000-0000-0000-000000000000"
                          ]
                        }
                      },
                      "required": [
                        "category",
                        "part_id"
                      ]
                    }
                  }
                },
                "required": [
                  "items"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemesItemsReplace"
      }
    },
    "/v1/admin/themes/{theme_code}/gift": {
      "post": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマをプレゼント配布 / Gift Theme to Users",
        "description": "### 用途\n指定ユーザーにテーマをプレゼント配布する。`gift_theme_to_user` RPC を呼び出し、\n`user_themes` への追加・通知発火・贈り物履歴の記録などをアトミックに実施する。\nメッセージ（最大 500 文字）を添えられる。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細画面「テーマをプレゼント」モーダル\n- サポート対応の補填配布（不具合お詫び等)\n- キャンペーン当選者への一括配布（ループ呼び出し）\n\n### 認証・認可\n`requireAdmin`。配布者は JWT から取れる `adminId` を RPC に渡し、贈り元として記録される。\n\n### 挙動・制約\n- DB 側 `gift_theme_to_user(p_theme_id, p_admin_id, p_user_id, p_message)` をコール\n- 既に所有しているユーザーへの再配布は RPC 側でハンドリング（重複追加しない／エラーを返す）\n- 監査ログ `theme.gift` に user_id とメッセージ有無を記録\n- レスポンスは RPC 戻り値をそのまま返す（`z.unknown()`）\n\n### 関連\n- `GET /v1/admin/themes` — 所有数 (`owners_count`) はここで増える",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9_]+$"
            },
            "required": true,
            "name": "theme_code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "user_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "配布結果",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemesGiftCreate"
      }
    },
    "/v1/admin/theme-parts": {
      "get": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマパーツ一覧 / Theme Part List",
        "description": "### 用途\nテーマを構成する個別パーツ（pin / icon / loading 等）の一覧。色設定 / 紐付くアセット 3 種\n（pin_asset / icon_asset / loading_asset）/ タグ / 利用テーマ数を 1 リクエストで返す。\n\n### 管理者ポータルでの使用タイミング\n- ゲーミフィケーション管理 > テーマパーツ一覧\n- カテゴリタブ切替（pin / icon / loading 等）\n- テーマ編集画面で「スロットに割り当てるパーツを選ぶ」モーダル\n- 利用状況確認（`used_in_themes_count` で参照されているテーマ数を可視化）\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- ページング: `PageQuerySchema`\n- 並び順: `category ASC, sort_order ASC`\n- アセット 3 種は LEFT JOIN で取得し、それぞれ `pin_asset` / `icon_asset` / `loading_asset` に整形\n- タグは `customization_theme_part_tags` から `jsonb_agg` で配列化\n- 利用テーマ数は post-fetch GROUP BY で計算（N+1 回避）\n\n### 関連\n- `POST /v1/admin/theme-parts` — パーツ作成\n- `PUT /v1/admin/theme-parts/{id}/tags` — タグ一括置換\n- `PUT /v1/admin/themes/{id}/items` — テーマへの割当",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminThemePart"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemePartsList"
      },
      "post": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマパーツを作成 / Create Theme Part",
        "description": "### 用途\n新規テーマパーツを `customization_theme_parts` に登録する。カテゴリ / 主色 / アクセント色 /\nアセット 3 種の ID / ローディング種別 / 公開フラグ / 並び順を含む。\n\n### 管理者ポータルでの使用タイミング\n- テーマパーツ管理 > 「新規パーツ」ボタン\n- アセットアップロード後の登録\n- 既存パーツの複製 → 色違いバリエーション作成\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- ボディ `passthrough`\n- アセット ID（pin / icon / loading）は事前に assets API でアップロード済みのもの\n- タグは別 API `PUT /{id}/tags` で後付け\n- 監査ログ `theme_part.create` に category を記録\n\n### 関連\n- `PUT /v1/admin/theme-parts/{id}/tags` — タグ設定\n- `PATCH /v1/admin/theme-parts/{id}` — 部分更新",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminThemePartUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminThemePart"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemePartsCreate"
      }
    },
    "/v1/admin/theme-parts/{id}": {
      "patch": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマパーツを更新 / Update Theme Part",
        "description": "### 用途\nテーマパーツを部分更新する。色変更・アセット差し替え・公開フラグ切替・並び順変更などに\n使う。空ボディは現行値返却。\n\n### 管理者ポータルでの使用タイミング\n- パーツ編集画面「保存」\n- カラーピッカーで色を変えた直後\n- アセット差し替え（新しい pin 画像をアップして `pin_asset_id` を更新）\n- 公開フラグ（`is_active`）のインライン切替\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- 該当 ID なしは `not_found`（404）\n- 既にテーマで使われているパーツの変更は、所有ユーザーの見た目に即影響する点に注意\n- 監査ログ `theme_part.update` に diff を記録\n\n### 関連\n- `PUT /v1/admin/theme-parts/{id}/tags` — タグ更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminThemePartUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminThemePart"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemePartsUpdate"
      },
      "delete": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "テーマパーツを削除 / Delete Theme Part",
        "description": "### 用途\nテーマパーツを物理削除する。テーマで参照されている場合は FK 制約で失敗するため、先に\n`PUT /v1/admin/themes/{id}/items` で割当を外しておく必要がある。\n\n### 管理者ポータルでの使用タイミング\n- パーツ編集画面「削除」確認モーダル承認後\n- 未使用パーツのクリーンアップ運用\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- 物理削除（DELETE）\n- `customization_theme_part_tags` の連動削除は DB スキーマ依存（CASCADE 設定確認）\n- 監査ログ `theme_part.delete` に削除前の name を残す\n- 存在しない ID でも 204（冪等）\n\n### 関連\n- `PATCH /v1/admin/theme-parts/{id}` — 非公開化（履歴を残す場合）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemePartsDelete"
      }
    },
    "/v1/admin/theme-parts/{id}/tags": {
      "put": {
        "tags": [
          "テーマ / Themes"
        ],
        "summary": "パーツのタグを一括置換 / Replace Part Tags",
        "description": "### 用途\nパーツに付与されているタグ（`customization_theme_part_tags`）を **一括置換** する。\nタグは検索・フィルタ・関連パーツ提案などで使うラベル群。既存タグはトランザクション内で\n全削除 → 全 INSERT する。\n\n### 管理者ポータルでの使用タイミング\n- パーツ編集画面のタグ入力フィールドで「保存」\n- タグサジェストから選択した内容のコミット\n- 空配列を送ってタグをクリア\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動・制約\n- DELETE → INSERT を 1 トランザクションで実行\n- `tags: []` で全クリア\n- 監査ログ `theme_part.update_tags` にタグ配列と件数を記録\n\n### 関連\n- `GET /v1/admin/theme-parts` — `tags` フィールドで現在値が確認可",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "tags"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "件数",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "count"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminThemePartsTagsReplace"
      }
    },
    "/v1/admin/ai-providers": {
      "get": {
        "tags": [
          "AI / AI"
        ],
        "summary": "AIプロバイダー一覧 / AI Provider List",
        "description": "### 用途\n登録済み **AI プロバイダー**（Claude / Gemini / OpenAI 等）の設定一覧を返す。\n`priority` の高い順に並ぶフォールバックチェーンとして使われる。\n\n### 管理者ポータルでの使用タイミング\n- 「システム > AI プロバイダー」画面の初期表示\n- 有効/無効、優先度、モデル、Vault 連携状況を一覧で確認するとき\n- フォールバック順序の見直し前のスナップショット取得\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`ORDER BY priority DESC` で固定。返却フィールドの `vault_secret_id` は API キー本体を\n返さず Vault の参照 UUID のみ（シークレットはサーバー側で実行時に解決）。\nページング枠で包んで返すが現状は 1 ページ全量。\n\n### 関連\n- `PATCH /v1/admin/ai-providers/{id}` — 設定変更\n- `POST /v1/admin/ai-providers/{id}/register-key` — API キー Vault 登録\n- `GET /v1/admin/ai-usage-logs` — 使用量・コスト確認",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AiProvider"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAiProvidersList"
      }
    },
    "/v1/admin/ai-providers/{id}": {
      "patch": {
        "tags": [
          "AI / AI"
        ],
        "summary": "AIプロバイダー設定を更新 / Update AI Provider",
        "description": "### 用途\nAI プロバイダーの表示名・モデル名・有効フラグ・優先度・フリーフォーム `config` を\n部分更新する。**API キー本体はここでは扱わない**（専用の register-key を使用）。\n\n### 管理者ポータルでの使用タイミング\n- 「AI プロバイダー」画面で行のトグル/モデル切替/優先度変更を保存\n- フォールバック順を `priority` で並べ替えてコミット\n- プロバイダー固有 `config`（タイムアウト、temperature、max_tokens 等）を編集\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`updated_at` をサーバー側で `new Date().toISOString()` に強制セットして UPDATE する。\n存在しない `id` は `not_found` (404)。成功時は `admin_activity_logs` に\n`ai_provider.update` として diff を記録（`provider_key` をラベル化）。\n\n### 関連\n- `POST /v1/admin/ai-providers/{id}/register-key` — API キーを Vault に登録",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "display_name": {
                    "type": "string"
                  },
                  "model_name": {
                    "type": "string"
                  },
                  "is_enabled": {
                    "type": "boolean"
                  },
                  "priority": {
                    "type": "integer"
                  },
                  "config": {
                    "type": "object",
                    "description": "任意の JSON 値（string / number / boolean / null / array / object）。再帰構造は object + additionalProperties で表現。",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiProvider"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAiProvidersUpdate"
      }
    },
    "/v1/admin/ai-providers/{id}/register-key": {
      "post": {
        "tags": [
          "AI / AI"
        ],
        "summary": "APIキーをVaultに登録 / Register API Key in Vault",
        "description": "### 用途\nAI プロバイダーの **API キーを Supabase Vault に保存** し、`ai_providers.vault_secret_id`\nに保存先 UUID を紐付ける。生 API キーはサーバー / DB に平文では保持しない設計。\n\n### 管理者ポータルでの使用タイミング\n- 「AI プロバイダー」画面で「API キー登録」ダイアログを開いて貼り付け→保存\n- キーローテーション時の新キー登録（登録後は `vault_secret_id` を差し替え、Vault 側の旧シークレットは別途破棄する）\n- 新規プロバイダー有効化前のキー初期投入\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。Vault 書き込み RPC は DB スーパーユーザー\n（Hyperdrive 接続の postgres）として実行する。\n\n### 挙動・制約\nVault 名は `ai_provider_<provider_key>_<timestamp>` で衝突回避。`vault_insert_secret` RPC が\n新シークレット UUID を返したら `ai_providers.vault_secret_id` を UPDATE。`api_key` の最低長は\n10 文字。`admin_activity_logs` に `ai_provider.register_key`（`vault_secret_id` メタ付き）として\n記録するが、キー本体は一切ログに残さない。\n\n### 関連\n- `PATCH /v1/admin/ai-providers/{id}` — キー以外の設定更新\n- `GET /v1/admin/ai-usage-logs` — 登録後の利用状況・コスト計測",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "api_key": {
                    "type": "string",
                    "minLength": 10
                  }
                },
                "required": [
                  "api_key"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "登録済み",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "vault_secret_id": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "required": [
                    "ok",
                    "vault_secret_id"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAiProvidersRegisterKeyCreate"
      }
    },
    "/v1/admin/ai-usage-logs": {
      "get": {
        "tags": [
          "AI / AI"
        ],
        "summary": "AI使用ログ一覧 / AI Usage Log List",
        "description": "### 用途\n`ai_usage_logs` は **Parky ドメイン固有** の LLM イベントログ\n(parsed_query / response_status / fallback_reason / session_id) を返す。\n\n### Cloudflare AI Gateway との責務分離\nサーバーの LLM 呼び出しはすべて `AI_GATEWAY_BASE_URL` 経由のため、**トークン数・推定コスト・\nレイテンシ・モデル別集計** は Cloudflare AI Gateway ダッシュボードで確認する。\n本エンドポイントはそれらの重複指標を返さない。\n\n### 管理者ポータルでの使用タイミング\n- 「AI プロバイダー > 使用ログ」タブで直近のリクエストとステータスを確認\n- `response_status=error` + `error_code` でエラー発生の要因分析\n- `attempt_number > 1` のログでフォールバック連鎖発火の追跡\n- `session_id` でユーザー個別の AI 検索セッションをトレース\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用エンドポイント。\n\n### 挙動・制約\n`ORDER BY id DESC` で最新順。総件数は別 COUNT クエリ。\n\n### 関連\n- `GET /v1/admin/ai-providers` — プロバイダー設定の確認\n- `PATCH /v1/admin/ai-providers/{id}` — フォールバック順や有効/無効の調整",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "provider_key",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AiUsageLog"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAiUsageLogsList"
      }
    },
    "/v1/admin/codes": {
      "get": {
        "tags": [
          "コード / Codes"
        ],
        "summary": "コード一覧 / Code List",
        "description": "### 用途\nコードマスター（`codes`）を管理用フィールド付きで返す。`id` と `is_deleted` を含むので、\n公開向けの `/v1/codes` よりも編集に適する。`category_id`（admin_status / vehicle_type 等）\n× `code` × `lang` で列挙値を一元管理する。\n\n### 管理者ポータルでの使用タイミング\n- システム > コードマスター画面を開いたとき\n- 削除済みコードも表示するトグル ON 時（`include_deleted=true`）\n- 言語切替（`lang=ja|en`）時\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 並び順: `category_id ASC, sort_order ASC`\n- `lang` 既定 `ja`、`include_deleted` 既定 `false`\n- ページングなしで全件返す（既存 UI が一覧で扱う前提）\n\n### 関連\n- `POST /v1/admin/codes/` — 新規追加\n- `PATCH /v1/admin/codes/{id}` — 編集\n- `GET /v1/codes` — 公開エンドポイント（クライアント向け）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "examples": [
                "ja"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "コード一覧（管理用）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminCode"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminCodesList"
      },
      "post": {
        "tags": [
          "コード / Codes"
        ],
        "summary": "コードを作成 / Create Code",
        "description": "### 用途\n新しいコードマスター行を追加する。CHECK 制約に新コードを許可するためのマイグレーションは\n別途必要だが、画面表示用ラベル追加・並び順調整はこの API のみで完結する。\n\n### 管理者ポータルでの使用タイミング\n- コードマスター画面で「新規追加」ボタン押下時\n- 多言語対応で同じ `code` の別言語ラベルを追加するとき\n- 新カテゴリ（例: `sponsor_category`）を運用開始するとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `lang` 既定 `ja`、`sort_order` は省略可（NULL 保存）\n- 同一 `(category_id, code, lang)` の重複は DB UNIQUE 制約で弾かれ 409 を返す\n- 監査ログ `code.create` を `category.code` ラベル付きで記録\n\n### 関連\n- `PATCH /v1/admin/codes/{id}` — 表示ラベル変更\n- `DELETE /v1/admin/codes/{id}` — 完全削除",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "category_id": {
                    "type": "string",
                    "minLength": 1
                  },
                  "code": {
                    "type": "string",
                    "minLength": 1
                  },
                  "display_label": {
                    "type": "string",
                    "minLength": 1
                  },
                  "lang": {
                    "type": "string",
                    "default": "ja"
                  },
                  "sort_order": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                },
                "required": [
                  "category_id",
                  "code",
                  "display_label"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminCode"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminCodesCreate"
      }
    },
    "/v1/admin/codes/{id}": {
      "patch": {
        "tags": [
          "コード / Codes"
        ],
        "summary": "コードを更新 / Update Code",
        "description": "### 用途\nコードマスター行を部分更新する。表示ラベル・並び順・論理削除フラグの切り替えに使う。\n空ボディは現在値返却（pre-fetch にも使える）。\n\n### 管理者ポータルでの使用タイミング\n- コードマスター画面のインライン編集確定時\n- `is_deleted=true` で論理削除する操作（ゴミ箱に入れる）\n- `sort_order` の並び替えコミット時\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 該当 ID なしは 404 `not_found`\n- 監査ログ `code.update` に `category.code` ラベル + diff を記録\n- `code` 値の変更は CHECK 制約と相性悪いので推奨しない（実装上は許可）\n\n### 関連\n- `DELETE /v1/admin/codes/{id}` — ハード削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CodeUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminCode"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminCodesUpdate"
      },
      "delete": {
        "tags": [
          "コード / Codes"
        ],
        "summary": "コードを削除 / Delete Code",
        "description": "### 用途\nコードマスター行を物理削除する。論理削除（`is_deleted=true`）したい場合は\n`PATCH /v1/admin/codes/{id}` を使うこと。\n\n### 管理者ポータルでの使用タイミング\n- コードマスターのゴミ箱画面で「完全削除」ボタン押下時\n- 一度も使われていない誤登録コードを除去するとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `DELETE FROM codes WHERE id = $1`（FK 違反時はエラー）\n- 削除前に `category_id.code` をフェッチして監査ログラベルに保存\n- 監査ログ `code.delete` に `metadata.soft=false` で記録\n\n### 関連\n- `PATCH /v1/admin/codes/{id}` — `is_deleted=true` で論理削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminCodesDelete"
      }
    },
    "/v1/admin/dashboard/metrics": {
      "get": {
        "tags": [
          "ダッシュボード / Dashboard"
        ],
        "summary": "ダッシュボード KPI / Dashboard KPI",
        "description": "### 用途\n管理者ホーム画面の KPI カード 6 種を 1 リクエストで返す。総ユーザー数、駐車中セッション数、\n登録駐車場数、本日売上、未対応エラーレポート数、未対応レビュー数。\n\n### 管理者ポータルでの使用タイミング\n- ダッシュボード画面のロード時\n- ヘッダーの更新ボタン押下時のリフレッシュ\n- 一定間隔（例: 60 秒）の自動更新ポーリング\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `admin_dashboard_metrics_mv` (pg_cron 5 分毎 REFRESH) を 1 SELECT で取得\n- MV 未作成 / 参照失敗時は 6 クエリ並列発行の従来ロジックへ fallback\n- `today_sales` は `parking_sessions.ended_at >= date_trunc('day', NOW())` の合計\n- `active_sessions` は `status = 'parking'`、`pending_*` は `status = 'new' / 'pending'`\n- `_meta` envelope を返す (admin portal API audit 2026-04-23 ADMIN-M-08)。\n  `_meta.reason` で MV 由来 (`materialized_view`) か fallback クエリ由来 (`direct_query`) かを示す。\n  両者とも `synced: true` / `source: \"db\"` を返す（実データのため）。\n\n### 関連\n- 個別の詳細リスト API（駐車セッション・レビュー・エラーレポート各エンドポイント）",
        "responses": {
          "200": {
            "description": "KPI",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users_total": {
                      "type": "integer"
                    },
                    "active_sessions": {
                      "type": "integer"
                    },
                    "parking_lots_total": {
                      "type": "integer"
                    },
                    "today_sales": {
                      "type": "number"
                    },
                    "pending_error_reports": {
                      "type": "integer"
                    },
                    "pending_reviews": {
                      "type": "integer"
                    },
                    "_meta": {
                      "$ref": "#/components/schemas/SyncMeta"
                    }
                  },
                  "required": [
                    "users_total",
                    "active_sessions",
                    "parking_lots_total",
                    "today_sales",
                    "pending_error_reports",
                    "pending_reviews",
                    "_meta"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminDashboardMetrics"
      }
    },
    "/v1/admin/activity-log-refs/resolve": {
      "post": {
        "tags": [
          "監査ログ / Audit Logs"
        ],
        "summary": "ログ参照IDを一括解決 / Bulk Resolve Log Reference IDs",
        "description": "### 用途\n監査ログのタイムライン表示で、`target_id` カラムに含まれる複数種類の UUID を\n**1 リクエストで一括解決**し、それぞれの表示用レコード（駐車場名・ユーザー名等）を返す。\nN+1 クエリを避けるためのバッチ取得ヘルパ。\n\n### 管理者ポータルでの使用タイミング\n- アクティビティログ一覧を取得した直後、ID 群を抽出してからまとめて呼ぶ\n- ダッシュボードのタイムラインカードのレンダリング前\n- 複合検索結果の表示テーブルで参照先ラベルを埋めるとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 6 種類のテーブル（parking_lots / app_users / parking_reviews /\n  user_activity_logs / error_reports / support_tickets）を `id = ANY($1::uuid[])` で取得\n- 各種類の配列は省略可（指定キーのみ実行、未指定は空配列）\n- レスポンスはキー固定の 6 配列を必ず返す（クライアント側で undefined チェック不要）\n\n### 関連\n- `GET /v1/admin/activity-logs/` — 解決前の生ログ一覧",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parking_lot_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "parking_review_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "user_activity_log_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "error_report_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  },
                  "support_ticket_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "examples": [
                        "00000000-0000-0000-0000-000000000000"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "解決結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parking_lots": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "name": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name"
                        ]
                      }
                    },
                    "app_users": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "display_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "display_name"
                        ]
                      }
                    },
                    "parking_reviews": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "comment": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "rating": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "user_id",
                          "comment",
                          "rating"
                        ]
                      }
                    },
                    "user_activity_logs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "activity_type": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "activity_type"
                        ]
                      }
                    },
                    "error_reports": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "parking_lot_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "description": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "parking_lot_name",
                          "description"
                        ]
                      }
                    },
                    "support_tickets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "examples": [
                              "00000000-0000-0000-0000-000000000000"
                            ]
                          },
                          "subject": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "subject"
                        ]
                      }
                    }
                  },
                  "required": [
                    "parking_lots",
                    "app_users",
                    "parking_reviews",
                    "user_activity_logs",
                    "error_reports",
                    "support_tickets"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminActivityLogRefsResolve"
      }
    },
    "/v1/admin/parking-sessions": {
      "get": {
        "tags": [
          "駐車セッション / Parking Sessions"
        ],
        "summary": "駐車セッション一覧 / Parking Session List",
        "description": "### 用途\n全ユーザーの駐車セッション（`parking_sessions`）をページング付きで取得する。\nユーザー（`app_users`）と駐車場（`parking_lots`）を LEFT JOIN し、表示に必要な\nラベル（display_name / email / 駐車場名 / 住所）をネストして返す。\n\n### ⚠️ DEPRECATED\n**このエンドポイントは 2027-01-01 に廃止予定**。`GET /cursor` に移行してください。\n`parking_sessions` は蓄積型で深い OFFSET が遅くなるため cursor 版 keyset pagination に移行する。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場管理 → 「駐車セッション」画面の初期ロード\n- ステータス絞り込み（active / closed 等）、ユーザー絞り込み、駐車場絞り込み\n- メモ全文検索（`memo ILIKE %keyword%`）でサポート問合せ調査\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車セッション閲覧権限を持つロールが必要。\n\n### 挙動・制約\n- 並び順: `started_at DESC NULLS LAST`, `created_at DESC`\n- ページング: `PageQuerySchema` 経由で `page` / `limit` を解決\n- フィルタ: `status` / `user_id` / `parking_lot_id`（完全一致）, `memo`（ILIKE 部分一致）\n- LEFT JOIN なので削除済みユーザー / 駐車場は `null` で返る\n- 集計用に `COUNT(*)` を並列実行して `total` を返却\n\n### 関連\n- `POST /v1/admin/parking-sessions` — 管理者代理でのセッション作成\n- `PATCH /v1/admin/parking-sessions/{id}` — メモ・ステータス等の更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "memo",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminParkingSessionWithRelations"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSessionsList"
      },
      "post": {
        "tags": [
          "駐車セッション / Parking Sessions"
        ],
        "summary": "駐車セッションを作成 / Create Parking Session",
        "description": "### 用途\n管理者がユーザーの代理で駐車セッションを作成する。サポート問合せ対応・\nイベント時の手動入力・テスト目的での投入などに使う。\n\n### 管理者ポータルでの使用タイミング\n- 駐車セッション画面の「新規追加」ボタン → モーダルで user / lot / 日時を入力\n- ユーザー詳細画面から「このユーザーのセッションを追加」\n- 過去セッションの遡及登録（CSV インポート前段の単発投入）\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車セッション編集権限を持つロールが必要。\nユーザー権限ではなく管理者権限で `user_id` を任意指定できる点がユーザー側 API との違い。\n\n### 挙動・制約\n- body は `passthrough()` で拡張カラムを許容（料金・車両・メモ等を一括投入可）\n- `INSERT INTO parking_sessions ${sql(body)} RETURNING *` の動的展開\n- ステータスはコードマスター値（`session_status`）に従うこと\n- 副作用: `admin_activity_logs` に `parking_session.create` と user / lot / status を記録\n\n### 関連\n- `GET /v1/admin/parking-sessions` — 一覧取得\n- `PATCH /v1/admin/parking-sessions/{id}` — 既存セッション更新",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "parking_lot_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "parking_spot_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "started_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "ended_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "type": "string"
                  },
                  "total_amount_minor": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "memo": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "user_id",
                  "parking_lot_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingSession"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSessionsCreate"
      }
    },
    "/v1/admin/parking-sessions/cursor": {
      "get": {
        "tags": [
          "駐車セッション / Parking Sessions"
        ],
        "summary": "駐車セッション一覧（Cursor）/ Parking Session List (Cursor)",
        "description": "### 用途\n`parking_sessions` を cursor ベースで取得する。offset 版 `GET /` は 2027-01-01 廃止予定。\n\n### 並び\n`started_at DESC NULLS LAST, id DESC` の keyset。NULL started_at 行は末尾にまとまる想定で、\nNULL 行を跨ぐページングはサポートしない (非 NULL 行だけで cursor を進める運用)。\n\n### cursor の使い方\n1. 初回は `cursor` パラメータを省略。\n2. レスポンスの `next_cursor` が `null` でなければ、次のリクエストに `cursor=<value>` を渡す。\n3. `has_more=false` になったら終端。\n\n### フィルタ\n- `status`: ステータス完全一致\n- `user_id`: ユーザー UUID 完全一致\n- `parking_lot_id`: 駐車場 UUID 完全一致\n- `memo`: `memo ILIKE %keyword%`\n\n### 認証・認可\n`requireAdmin` 必須。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "memo",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminParkingSessionWithRelations"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSessionsCursorList"
      }
    },
    "/v1/admin/parking-sessions/{id}": {
      "patch": {
        "tags": [
          "駐車セッション / Parking Sessions"
        ],
        "summary": "駐車セッションを更新 / Update Parking Session",
        "description": "### 用途\n既存セッションの任意カラムを部分更新する。サポート対応でのメモ追記、\nステータス強制クローズ、料金訂正などに使う。\n\n### 管理者ポータルでの使用タイミング\n- セッション詳細モーダルでメモ入力 → 保存\n- ステータスを `closed` / `cancelled` 等に手動遷移\n- 料金・終了時刻の訂正\n\n### 認証・認可\n`requireAdmin` 必須。RBAC で駐車セッション編集権限を持つロールが必要。\n\n### 挙動・制約\n- body が空オブジェクトの場合は DB を触らず現在値を取得して返す\n- 該当 ID が存在しない場合は 404 `not_found`\n- `UPDATE ... SET ${sql(body)}` で動的に列展開、未指定カラムは現行値を維持\n- 副作用: `admin_activity_logs` に `parking_session.update` と diff・最終 status を記録\n\n### 関連\n- `GET /v1/admin/parking-sessions` — 一覧取得（更新後の再取得用）\n- `POST /v1/admin/parking-sessions` — 新規作成",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingSessionUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingSession"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingSessionsUpdate"
      }
    },
    "/v1/admin/search/ai-parse": {
      "post": {
        "tags": [
          "検索 / Search"
        ],
        "summary": "AI検索クエリを解析 / Parse AI Search Query",
        "description": "### 用途\n管理者向け検索画面で、自然言語の発話を構造化検索フィルタ（JSON）に変換する\nエンドポイント。旧 Supabase Edge Function `ai-search` の置き換え。\n`lib/llm/freeform.ts` の `callFreeformLlm` 経由で Claude / OpenAI / Gemini の\nいずれかに委譲する。プロバイダ未設定 / LLM 失敗時は空フィルタ `{}` +\n`_meta: { synced: false, source: 'stub', ... }` を返し、呼出し側は fallback 検索にフォールスルーする。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場管理 > AI 検索欄にテキスト入力 → 送信ボタン\n- 「この駅の周辺で屋根付き」などをフィルタ JSON に変換\n\n### 認証・認可\n要 `requireAdmin`。\n\n### 挙動・制約\n- 入力: `{ action: 'parse', message: string }`\n- 出力: `{ filters: {...}, explanation?: string, _meta?: SyncMeta }`\n  - 成功時: `_meta` は省略（filters は LLM 実応答）\n  - 失敗時: `_meta.synced=false` + `source='stub'` + `reason` に原因\n- LLM が返した JSON の parse に失敗した場合は `filters: {}` + `_meta` で stub 通知\n- エンドユーザー版は `POST /v1/search/ai` で別実装（そちらは稼働中）\n- _meta envelope 採用 (admin portal API audit 2026-04-23 ADMIN-L-04)\n\n### 関連\n- `POST /v1/search/ai` — エンドユーザー向け AI 検索（稼働中）\n- `GET /v1/admin/ai-providers` — AI プロバイダ設定管理\n- `src/lib/llm/freeform.ts` — プロバイダ切替ロジック",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "parse"
                    ],
                    "default": "parse"
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "パース結果（filters は空オブジェクトを返すことがある）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "filters": {
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "explanation": {
                      "type": "string"
                    },
                    "_meta": {
                      "$ref": "#/components/schemas/SyncMeta"
                    }
                  },
                  "required": [
                    "filters"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminSearchAiParseCreate"
      }
    },
    "/v1/admin/instagram/categories": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドカテゴリ一覧 / Slide Category List",
        "description": "### 用途\nInstagram スライドテンプレートを分類する **スライドカテゴリ**（cover / spot_detail / ranking / cta など）の一覧を返す。\nテンプレート登録画面のカテゴリセレクター、フィルタリング、AI 自動生成時の種別判定に使う。\nソフトデリート済み（`is_deleted = 1`）は含まれず、`sort_order` 昇順で返す。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > テンプレート管理画面の「カテゴリ」セレクター描画時\n- スライドカテゴリ管理画面（CRUD）を開いたとき\n- 投稿一括生成ウィザードでテンプレ種別を選ばせるとき\n\n### 認証・認可\n`requireAdmin` 必須（管理者ポータルからのみアクセス可）。追加の権限スコープは不要。\n\n### 挙動・制約\nCloudflare D1（`INSTAGRAM_DB` binding）の `ig_slide_categories` テーブルを SELECT。\nParky Supabase とは完全分離されたデータストア。\n\n### 関連\n- `POST /v1/admin/instagram/categories` — 新規カテゴリ追加\n- `PATCH /v1/admin/instagram/categories/{code}` — ラベル/プレフィックス更新\n- `DELETE /v1/admin/instagram/categories/{code}` — ソフトデリート",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgSlideCategory"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramCategoriesList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドカテゴリを作成 / Create Slide Category",
        "description": "### 用途\n新しいスライドカテゴリを登録する。`code`（種別キー）はテンプレートとリンクする一意 ID で、\n`label`（日本語表示名）、`prefix`（命名規則のヒント）、`sort_order` を指定する。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 設定 > スライドカテゴリ管理 > 「+ 追加」ダイアログの保存時\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`ig_slide_categories` に INSERT。`sort_order` 未指定時は 50 で挿入。`code` は UNIQUE 想定（重複時は SQLite が制約違反を返す）。\n\n### 関連\n- `GET /v1/admin/instagram/categories` — 一覧取得\n- `PATCH /v1/admin/instagram/categories/{code}` — 更新",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1
                  },
                  "prefix": {
                    "type": "string"
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                },
                "required": [
                  "code",
                  "label"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlideCategory"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramCategoriesCreate"
      }
    },
    "/v1/admin/instagram/categories/{code}": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドカテゴリを更新 / Update Slide Category",
        "description": "### 用途\n既存スライドカテゴリのラベル / プレフィックス / 並び順を部分更新する。`code` は変更不可。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 設定 > スライドカテゴリ管理 > 行のインライン編集 / モーダル保存時\n- ドラッグ&ドロップで `sort_order` を入れ替えたとき\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n送信されたフィールドだけ動的に SET 句を組み立てて UPDATE。`updated_at` は自動更新。\n対象が存在しない場合は 404 `not_found`。\n\n### 関連\n- `GET /v1/admin/instagram/categories` — 一覧\n- `DELETE /v1/admin/instagram/categories/{code}` — 削除",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "prefix": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlideCategory"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramCategoriesUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドカテゴリをソフト削除 / Soft Delete Slide Category",
        "description": "### 用途\nスライドカテゴリを論理削除する（`is_deleted = 1`）。物理削除はせず、過去のテンプレが参照している\n履歴コードを壊さないようにする。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 設定 > スライドカテゴリ管理 > 行のゴミ箱アイコンタップ\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`UPDATE ig_slide_categories SET is_deleted = 1` のみ。テンプレ側の `slide_type` は影響を受けない。\n存在しない `code` を指定しても 200 を返す（冪等）。\n\n### 関連\n- `POST /v1/admin/instagram/categories` — 同じ code で再作成は不可（必要なら別 code で）",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "削除完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramCategoriesDelete"
      }
    },
    "/v1/admin/instagram/post-categories": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿カテゴリ一覧 / Post Category List",
        "description": "### 用途\n投稿（キャンペーン）単位の分類タグ「**投稿カテゴリ**」一覧を返す。\n「特集」「リール」「キャンペーン」などのジャンル分けで、ポータル側のフィルタや色分け表示に使う。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 投稿一覧画面のカテゴリフィルター\n- 投稿作成 / 編集モーダルの「カテゴリ」セレクター\n- 投稿カテゴリ管理画面の表示時\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\nD1 `ig_post_categories` から `is_deleted = 0` のレコードを `sort_order` 昇順で返す。\n`color` は HEX カラーコード（バッジの色付けに使用）。\n\n### 関連\n- `POST /v1/admin/instagram/post-categories` — 新規追加\n- `PATCH /v1/admin/instagram/posts/{id}` — `post_category_code` で投稿に紐付け",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgPostCategory"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostCategoriesList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿カテゴリを作成 / Create Post Category",
        "description": "### 用途\n新しい投稿カテゴリ（特集・リール・キャンペーン等）を登録する。\n`code` は投稿レコードの `post_category_code` と紐付く。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 設定 > 投稿カテゴリ管理 > 「+ 追加」モーダル保存\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`ig_post_categories` に INSERT。`color` は HEX 文字列（例: `#7c5cfc`）でバッジ色として使う。\n`sort_order` 未指定時は 50。\n\n### 関連\n- `GET /v1/admin/instagram/post-categories` — 一覧",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1
                  },
                  "color": {
                    "type": "string"
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                },
                "required": [
                  "code",
                  "label"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgPostCategory"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostCategoriesCreate"
      }
    },
    "/v1/admin/instagram/post-categories/{code}": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿カテゴリを更新 / Update Post Category",
        "description": "### 用途\n投稿カテゴリのラベル / 色 / 並び順を部分更新する。`code` は不変。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 設定 > 投稿カテゴリ管理 > インライン編集 / 並び替え\n- カラーピッカーでバッジ色を変更したとき\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n送信されたフィールドのみ動的 UPDATE。`updated_at` 自動更新。対象が存在しない場合 404。\n\n### 関連\n- `DELETE /v1/admin/instagram/post-categories/{code}` — ソフトデリート",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgPostCategory"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostCategoriesUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿カテゴリをソフト削除 / Soft Delete Post Category",
        "description": "### 用途\n投稿カテゴリを論理削除する（`is_deleted = 1`）。既存の投稿が `post_category_code` で参照していても\n履歴ラベルが残るよう物理削除はしない。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool > 設定 > 投稿カテゴリ管理 > 行のゴミ箱アイコン\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`UPDATE ig_post_categories SET is_deleted = 1`。冪等（存在しなくても 200）。\n\n### 関連\n- `GET /v1/admin/instagram/post-categories` — 一覧（ソフトデリート除外）",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "code",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "削除",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostCategoriesDelete"
      }
    },
    "/v1/admin/instagram/tags": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "タグ一覧 / Tag List",
        "description": "### 用途\nタグの一覧を返す（instagram_tag CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### クエリパラメータ\n`limit`/`offset` でページング、`q` で検索（hooks 実装による）。\n\n### 備考\n- レスポンスは `{ items, total }` 形式\n- 追加フィルタは hooks 側で解釈する",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgTag"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTagsList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "タグを作成 / Create Tag",
        "description": "### 用途\nタグを 1 件作成する（instagram_tag CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### 備考\n- ID はサーバー側で UUID 発行（hooks 実装依存）\n- admin_activity_logs への記録はこの endpoint では行わない",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "color": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgTag"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTagsCreate"
      }
    },
    "/v1/admin/instagram/tags/{slug}": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "タグ詳細 / Tag Detail",
        "description": "### 用途\n単一 タグの詳細を返す（instagram_tag CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`slug`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgTag"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTagsGet"
      },
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "タグを部分更新 / Partially Update Tag",
        "description": "### 用途\nタグを部分更新する（instagram_tag CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`slug`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgTag"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTagsUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "タグを削除 / Delete Tag",
        "description": "### 用途\nタグを削除する（instagram_tag CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`slug`: 対象 id\n\n### 備考\n- 存在しない id でも 200（冪等）\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "slug",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "削除成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTagsDelete"
      }
    },
    "/v1/admin/instagram/posts/{id}/tags": {
      "put": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿のタグを一括設定 / Bulk Set Post Tags",
        "description": "`tag_ids` を真として `ig_campaign_tags` の差分を計算し原子的に適用。`usage_count` も整合性保って増減。`requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tag_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "tag_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後タグ一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgTag"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsTagsReplace"
      }
    },
    "/v1/admin/instagram/templates/{id}/tags": {
      "put": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレートのタグを一括設定 / Bulk Set Template Tags",
        "description": "投稿側と同じ差分計算ロジックで `ig_template_tags` を更新。`requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tag_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "tag_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後タグ一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgTag"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesTagsReplace"
      }
    },
    "/v1/admin/instagram/templates": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレート一覧 / Template List",
        "description": "### 用途\nテンプレートの一覧を返す（instagram_template CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### クエリパラメータ\n`limit`/`offset` でページング、`q` で検索（hooks 実装による）。\n\n### 備考\n- レスポンスは `{ items, total }` 形式\n- 追加フィルタは hooks 側で解釈する",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgTemplate"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレートを作成 / Create Template",
        "description": "### 用途\nテンプレートを 1 件作成する（instagram_template CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### 備考\n- ID はサーバー側で UUID 発行（hooks 実装依存）\n- admin_activity_logs への記録はこの endpoint では行わない",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "slide_type": {
                    "type": "string"
                  },
                  "html_body": {
                    "type": "string",
                    "minLength": 1
                  },
                  "slot_schema": {
                    "type": "string",
                    "default": "[]"
                  },
                  "sample_content": {
                    "type": "string",
                    "default": "{}"
                  },
                  "sample_html": {
                    "type": "string",
                    "default": ""
                  },
                  "sort_order": {
                    "type": "integer",
                    "default": 0
                  },
                  "uses_parking_lot": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "parking",
                      "useful_info"
                    ]
                  }
                },
                "required": [
                  "code",
                  "name",
                  "slide_type",
                  "html_body"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesCreate"
      }
    },
    "/v1/admin/instagram/templates/{id}": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレート詳細 / Template Detail",
        "description": "### 用途\n単一 テンプレートの詳細を返す（instagram_template CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesGet"
      },
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレートを部分更新 / Partially Update Template",
        "description": "### 用途\nテンプレートを部分更新する（instagram_template CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "slide_type": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "html_body": {
                    "type": "string"
                  },
                  "slot_schema": {
                    "type": "string"
                  },
                  "sample_content": {
                    "type": "string"
                  },
                  "sample_html": {
                    "type": "string"
                  },
                  "sort_order": {
                    "type": "integer"
                  },
                  "is_active": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "uses_parking_lot": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "parking",
                      "useful_info"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレートを削除 / Delete Template",
        "description": "### 用途\nテンプレートを削除する（instagram_template CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 存在しない id でも 204（冪等）\n- 使用中は 409、`?force=1` で cascade 削除\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ]
            },
            "required": false,
            "name": "force",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "使用中のため削除不可",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "slide_count": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "error",
                    "slide_count"
                  ]
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesDelete"
      }
    },
    "/v1/admin/instagram/ai-providers": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "利用可能AIプロバイダー一覧 / Available AI Provider List",
        "description": "### 用途\nInstagram tool で使える AI プロバイダー（OpenAI / Anthropic / Workers AI 等）の最小情報を返す。\nセレクター描画用なので `vault_secret_id` 等のシークレット参照キーは含めない。\n\n### 管理者ポータルでの使用タイミング\n- AI 生成系操作（キャプション生成、スロット自動入力、HTML 修正等）の「プロバイダー選択」セレクター描画時\n- 初期表示時に priority 最高のものをデフォルト選択するため\n\n### 認証・認可\n`requireAdmin` 必須。Parky 共通の `ai_providers` テーブル（Supabase）を読むので Hyperdrive 経由。\n\n### 挙動・制約\n`SELECT id, provider_key, display_name, model_name, is_enabled, priority FROM ai_providers`\n`WHERE is_enabled = true ORDER BY priority DESC`。LLM 呼出本体は `loadProviders()` で\nconfig / vault_secret_id まで含めて別途取得する。\n\n### 関連\n- `POST /v1/admin/instagram/posts/{id}/generate-caption` — provider_id を指定可能\n- `POST /v1/admin/instagram/slides/{id}/generate-content` — provider_id を指定可能",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "provider_key": {
                        "type": "string"
                      },
                      "display_name": {
                        "type": "string"
                      },
                      "model_name": {
                        "type": "string"
                      },
                      "is_enabled": {
                        "type": "boolean"
                      },
                      "priority": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "id",
                      "provider_key",
                      "display_name",
                      "model_name",
                      "is_enabled",
                      "priority"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramAiProvidersList"
      }
    },
    "/v1/admin/instagram/templates/analyze-html": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "HTMLをAIでテンプレート化 / Analyze HTML with AI",
        "description": "### 用途\n既存のスライド HTML を LLM に食わせ、「可変部分」を `{{key}}` プレースホルダに\n置換した *テンプレート HTML* と、各プレースホルダの型定義 (`slot_schema`) を生成する。\n制作済みスライドを量産可能なテンプレートに昇格させる半自動ツール。\n\n### 管理者ポータルでの使用タイミング\n- Instagram tool のテンプレート作成画面で「既存 HTML から作る」選択時\n- ダッシュボードに貼った HTML スニペットに対する「テンプレ化」操作\n\n### 認証・認可\n要 `requireAdmin`。LLM 消費があるため `instagram:ai` 権限が望ましい。\n\n### 挙動・制約\n- LLM は `ai_providers` テーブルから取得（`provider_id` / `model` で指定可能、未指定なら優先順位 1 位）\n- 応答 JSON を抽出・検証し、`slot_schema.type` は text/textarea/number/url/image に正規化\n- JSON 不正時は `bad_gateway` (502) を返す\n- 使用量は `ai_usage_logs` に記録される（トークン数・コスト）\n- HTML 構造・CSS・class は完全保持、可変テキスト / 画像 URL / 数値のみ置換\n\n### 関連\n- `POST /v1/admin/instagram/post-templates` — テンプレート新規登録\n- `POST /v1/admin/instagram/templates/{id}/revise` — テンプレートを LLM でリバイズ",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "html": {
                    "type": "string",
                    "minLength": 10
                  },
                  "slide_type": {
                    "type": "string"
                  },
                  "hint": {
                    "type": "string"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                },
                "required": [
                  "html"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "解析結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "html_body": {
                      "type": "string"
                    },
                    "slot_schema": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "required": {
                            "type": "boolean"
                          },
                          "placeholder": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "key",
                          "label",
                          "type"
                        ]
                      }
                    },
                    "sample_content": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "html_body",
                    "slot_schema",
                    "sample_content"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesAnalyzeHtmlCreate"
      }
    },
    "/v1/admin/instagram/templates/{id}/revise": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレートをLLMで修正 / Revise Template with LLM",
        "description": "### 用途\nテンプレートの `html_body`（`{{key}}` プレースホルダ入り）または `sample_html`（完成形）を\nLLM で修正する。**保存せずプレビューのみ返却** — 管理者は差分を見てから手動で採用できる。\n\n### 管理者ポータルでの使用タイミング\n- テンプレート編集画面の「AI 提案」→「差分確認」→「採用 / 破棄」\n- 複数案生成して比較する導線\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `target='template'`: 修正時も `{{key}}` を**絶対に削除・改名しない**ようシステムプロンプトで制約\n- `target='sample'`: プレースホルダ制約なし（完成形を直に編集）\n- `current_html` 指定でユーザー未保存の修正を LLM に渡せる\n- 保存は別途 `PATCH /v1/admin/instagram/templates/{id}` で\n\n### 関連\n- `PATCH /v1/admin/instagram/templates/{id}` — 修正案を採用して保存",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instructions": {
                    "type": "string",
                    "minLength": 1
                  },
                  "target": {
                    "type": "string",
                    "enum": [
                      "template",
                      "sample"
                    ],
                    "default": "template"
                  },
                  "current_html": {
                    "type": "string"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                },
                "required": [
                  "instructions"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "修正案 HTML",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "html": {
                      "type": "string"
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "input_tokens": {
                          "type": "number"
                        },
                        "output_tokens": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "input_tokens",
                        "output_tokens"
                      ]
                    }
                  },
                  "required": [
                    "html",
                    "usage"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramTemplatesReviseCreate"
      }
    },
    "/v1/admin/instagram/post-templates": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿テンプレート一覧 / Post Template List",
        "description": "### 用途\n**投稿テンプレート**（複数のスライドテンプレを束ねた雛形）の一覧を返す。\n「カバー → スポット詳細×N → ランキング → CTA」のような定型ストーリーを 1 レコードに保持し、\n`slide_refs` JSON で構成される。\n\n### 管理者ポータルでの使用タイミング\n- 投稿一括生成ウィザードの「投稿テンプレを選ぶ」セレクター\n- 投稿テンプレート管理画面\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`SELECT id, code, name, description, slide_refs FROM ig_post_templates ORDER BY code ASC`。`slide_refs` は文字列 JSON のまま返す\n（クライアント側で `[{template_id, count}]` 形式に parse する）。\n\n### 関連\n- `POST /v1/admin/instagram/posts/{id}/generate-from-parking-lots` — テンプレを使った一括生成\n- `POST /v1/admin/instagram/posts/{id}/generate-all` — テンプレを使った AI 全体生成",
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgPostTemplate"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostTemplatesList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿テンプレートを作成 / Create Post Template",
        "description": "### 用途\n新しい投稿テンプレートを登録する。`slide_refs` には\n`[{\"template_id\":\"<uuid>\",\"count\":N}, ...]` の JSON 文字列を渡し、AI 一括生成時の組み立て順 / 枚数を定義する。\n\n### 管理者ポータルでの使用タイミング\n- 投稿テンプレート管理 > 「+ 新規」モーダルの保存\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\nid は `crypto.randomUUID()`。`slide_refs` は文字列保存（D1 に JSON 型は無いため）。\n`code` は識別子として UNIQUE 想定。\n\n### 関連\n- `PATCH /v1/admin/instagram/post-templates/{id}` — slide_refs 編集",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "description": {
                    "type": "string"
                  },
                  "slide_refs": {
                    "type": "string"
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "parking",
                      "useful_info"
                    ]
                  }
                },
                "required": [
                  "code",
                  "name",
                  "slide_refs"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgPostTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostTemplatesCreate"
      }
    },
    "/v1/admin/instagram/post-templates/{id}": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿テンプレートを更新 / Update Post Template",
        "description": "### 用途\n投稿テンプレートのフィールド（code / name / description / slide_refs）を部分更新する。\n`slide_refs` を編集すると、以後の一括生成で使われるスライド構成が変わる。\n\n### 管理者ポータルでの使用タイミング\n- 投稿テンプレート管理 > 編集モーダル保存\n- スライド構成のドラッグ&ドロップ並び替え保存\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n送信されたフィールドのみ動的 SET。空ボディは 400。`updated_at` 自動更新。404 if not found。\n\n### 関連\n- `DELETE /v1/admin/instagram/post-templates/{id}` — 物理削除",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "slide_refs": {
                    "type": "string"
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "parking",
                      "useful_info"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgPostTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostTemplatesUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "投稿テンプレートを削除 / Delete Post Template",
        "description": "### 用途\n投稿テンプレートを物理削除する。既に作成済みの投稿（キャンペーン）には影響しない\n（投稿側にはスライドが INSERT 済みで、テンプレへの FK は持たないため）。\n\n### 管理者ポータルでの使用タイミング\n- 投稿テンプレート管理画面のゴミ箱アイコン\n\n### 認証・認可\n`requireAdmin` 必須。\n\n### 挙動・制約\n`DELETE FROM ig_post_templates WHERE id = ?`。冪等。\n\n### 関連\n- `GET /v1/admin/instagram/post-templates` — 一覧",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "削除完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostTemplatesDelete"
      }
    },
    "/v1/admin/instagram/posts": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャンペーン一覧 / Campaign List",
        "description": "### 用途\nキャンペーンの一覧を返す（instagram_campaign CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### クエリパラメータ\n`limit`/`offset` でページング、`q` で検索（hooks 実装による）。\n\n### 備考\n- レスポンスは `{ items, total }` 形式\n- 追加フィルタは hooks 側で解釈する",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "include",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/IgCampaign"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "slides": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/IgSlide"
                                }
                              },
                              "caption_body": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "slide_count": {
                                "type": "number"
                              },
                              "first_slide_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "first_slide_png_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "first_slide_template_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "first_slide_content": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "first_slide_html_override": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            }
                          }
                        ]
                      }
                    },
                    "total": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "items",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャンペーンを作成 / Create Campaign",
        "description": "### 用途\nキャンペーンを 1 件作成する（instagram_campaign CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### 備考\n- ID はサーバー側で UUID 発行（hooks 実装依存）\n- admin_activity_logs への記録はこの endpoint では行わない",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "theme": {
                    "type": "string"
                  },
                  "area": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "source_material": {
                    "type": "string"
                  },
                  "post_category_code": {
                    "type": "string"
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "parking",
                      "useful_info"
                    ]
                  }
                },
                "required": [
                  "code",
                  "title"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgCampaign"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャンペーン詳細 / Campaign Detail",
        "description": "### 用途\n単一 キャンペーンの詳細を返す（instagram_campaign CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/IgCampaign"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "slides": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/IgSlide"
                          }
                        },
                        "caption": {
                          "$ref": "#/components/schemas/IgCaption"
                        }
                      },
                      "required": [
                        "slides",
                        "caption"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsGet"
      },
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャンペーンを部分更新 / Partially Update Campaign",
        "description": "### 用途\nキャンペーンを部分更新する（instagram_campaign CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "theme": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "area": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "review",
                      "scheduled",
                      "published",
                      "archived"
                    ]
                  },
                  "scheduled_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source_material": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "post_category_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "parking",
                      "useful_info"
                    ]
                  },
                  "code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgCampaign"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャンペーンを削除 / Delete Campaign",
        "description": "### 用途\nキャンペーンを削除する（instagram_campaign CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 存在しない id でも 204（冪等）\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsDelete"
      }
    },
    "/v1/admin/instagram/posts/{id}/slides": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライド一覧 / Slide List",
        "description": "### 用途\nキャンペーン配下のスライド (`ig_slides`) を `slide_index` 昇順で返す。\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `slide_index` 昇順、存在しなければ空配列",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgSlide"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsSlidesList"
      },
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドを追加 / Add Slide",
        "description": "### 用途\nキャンペーンに新しいスライドを追加する。`content` 未指定 / 空 / `{}` の場合は\ntemplate の `sample_content` を初期値として流し込む。\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- UUID 自動生成、created_at / updated_at は ISO\n- slide_index は呼出側指定（重複時は reorder が必要）",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template_id": {
                    "type": "string"
                  },
                  "slide_index": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "content": {
                    "type": "string",
                    "default": "{}"
                  }
                },
                "required": [
                  "template_id",
                  "slide_index"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlide"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsSlidesCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}/slides/reorder": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドを並び替え / Reorder Slides",
        "description": "### 用途\nスライドの表示順 (`slide_index`) を一括更新する。ドラッグ&ドロップ並び替え後の最終配列をまとめて送る。\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `UNIQUE(campaign_id, slide_index)` 制約を避けるため、一旦負の値ゾーン（`-1000000 - rowid`）にシフト → 本値に戻す 2 段戦略\n- D1 batch API で原子的に適用",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "slide_index": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "id",
                        "slide_index"
                      ]
                    }
                  }
                },
                "required": [
                  "items"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "更新成功"
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsSlidesReorder"
      }
    },
    "/v1/admin/instagram/slides/{id}": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドを部分更新 / Partially Update Slide",
        "description": "### 用途\nスライドを部分更新する（instagram_slide CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 未存在は 404 `not_found`\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string"
                  },
                  "html_override": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "png_r2_key": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "png_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "revision_notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "parking_lot_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlide"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesUpdate"
      },
      "delete": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドを削除 / Delete Slide",
        "description": "### 用途\nスライドを削除する（instagram_slide CRUD）。\n\n### 認証\n要 `requireAdmin`。\n\n### パスパラメータ\n`id`: 対象 id\n\n### 備考\n- 存在しない id でも 204（冪等）\n- admin_activity_logs への記録はこの endpoint では行わない",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesDelete"
      }
    },
    "/v1/admin/instagram/slides/{id}/apply-parking-lot": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドに駐車場を適用 / Apply Parking Lot to Slide",
        "description": "### 用途\nスライド単位で「この駐車場の情報を流し込む」運用。\n`parking_lots` テーブルから name / address / image / tags / 料金 等を取得し、\nテンプレの slot_schema に存在するキー（name, address, photo_url, category, description, price 等）へ決定論マッピングして `content` を更新する。\n\n### 挙動\n- `ig_slides.parking_lot_id` を更新\n- `content` の該当キーを上書き（無いキーは無視）\n- description は LLM に 1-2 文で生成させる（失敗しても slot 更新はする）\n- `html_override` は触らない（override 解除は別 PATCH で）",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parking_lot_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "fill_description": {
                    "type": "boolean"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                },
                "required": [
                  "parking_lot_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後スライド",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlide"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesApplyParkingLotCreate"
      }
    },
    "/v1/admin/instagram/slides/{id}/duplicate": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドを複製 / Duplicate Slide",
        "description": "### 用途\n既存スライドと同じ `template_id` / `content` / `html_override` を持つ新規スライドを\n作成し、元スライドの直後に挿入する（以降のスライドは +1 される）。PNG は意図的に複製しない\n（同じ画像が並ぶと混乱するため）。\n\n### 管理者ポータルでの使用タイミング\n- スライドエディタの「このスライドを複製」ボタン\n- 似たレイアウトのスライドを量産したいとき\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `UNIQUE(campaign_id, slide_index)` 衝突を避けるため、後続スライドを一時的に負の index\n  に逃してから挿入 → 最終 index に詰め直す 2 段階 batch\n- `revision_notes` は引き継がない（新規は空）\n- 存在しない id は 404 `not_found`\n\n### 関連\n- `POST /v1/admin/instagram/posts/{id}/slides` — ゼロから新規作成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "複製後の新スライド",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlide"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesDuplicate"
      }
    },
    "/v1/admin/instagram/slides/{id}/upload-png": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドPNGをアップロード / Upload Slide PNG",
        "description": "### 用途\nスライドをブラウザ側でレンダー→ PNG 化した画像を受け取り、R2 にアップロードする。\n同じキーに上書きすることで、再生成しても公開 URL が変わらず CDN キャッシュの再バインドも楽。\n\n### 管理者ポータルでの使用タイミング\n- スライドエディタの「PNG を確定」または「自動エクスポート」\n- バッチ書き出しツール\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- R2 キーは `posts/<campaign_code>/slides/<NN>.png`（番号 2 桁ゼロパディング）\n- 公開 URL は `/cdn/ig/<key>?v=<timestamp>` で返す（キャッシュバスタ付き）\n- `ig_slides.png_r2_key` / `png_url` を自動更新\n- `INSTAGRAM_R2` binding が無いと 503\n\n### 関連\n- `PATCH /v1/admin/instagram/slides/{id}/confirm-png` — 既存 R2 オブジェクトを公式採用として記録",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "campaign_code": {
                    "type": "string"
                  },
                  "slide_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "アップロード完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "r2_key": {
                      "type": "string"
                    },
                    "public_url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "r2_key",
                    "public_url"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesUploadPngCreate"
      }
    },
    "/v1/admin/instagram/upload-image": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "コンテンツ画像をアップロード / Upload Content Image",
        "description": "### 用途\nスライドのスロット（背景画像・商品写真など）に埋め込む汎用画像を R2 にアップロードする。\nファイル名と投稿コード (`campaign_code`) からタイムスタンプ+ハッシュ付き R2 キーを生成する。\n\n### 管理者ポータルでの使用タイミング\n- スライドエディタの画像スロットにドラッグ&ドロップ\n- 外部画像の差し替え導線\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- ファイル名サニタイズ（非 ASCII/危険文字を `_` に置換、40 文字まで）\n- R2 キーにはタイムスタンプ (`YYYYMMDD-HHMMSS`) と 8 文字 UUID ハッシュを含め衝突回避\n- 公開 URL は `/cdn/ig/<key>` で返る\n- `INSTAGRAM_R2` binding が無いと 503\n\n### 関連\n- `POST /v1/admin/instagram/slides/{id}/upload-png` — スライド本体の PNG 保存\n- `POST /v1/admin/instagram/images/detect-sensitive` — センシティブ領域検出",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "campaign_code": {
                    "type": "string"
                  },
                  "slide_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "アップロード完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "r2_key": {
                      "type": "string"
                    },
                    "public_url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "r2_key",
                    "public_url"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramUploadImageCreate"
      }
    },
    "/v1/admin/instagram/slides/{id}/confirm-png": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "PNGアップロード完了を記録 / Confirm PNG Upload",
        "description": "### 用途\nスライドに対応する R2 PNG の `r2_key` と `public_url` を DB に記録する確定用エンドポイント。\n`/upload-png` を使わずに別経路（presigned PUT 直送 / バッチ書き込み）でアップしたケースで使う。\n\n### 管理者ポータルでの使用タイミング\n- 外部ツール連携で PNG 書き出し済みの R2 オブジェクトを DB に紐付ける\n- バックフィルスクリプト\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `ig_slides.png_r2_key` / `png_url` / `updated_at` を UPDATE\n- R2 オブジェクトの存在検証は行わない（呼出し側責任）\n- 存在しない slide は 404 `not_found`\n\n### 関連\n- `POST /v1/admin/instagram/slides/{id}/upload-png` — Worker 経由でのアップロード + 自動確定",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "r2_key": {
                    "type": "string"
                  },
                  "public_url": {
                    "type": "string"
                  }
                },
                "required": [
                  "r2_key",
                  "public_url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IgSlide"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesConfirmPngUpdate"
      }
    },
    "/v1/admin/instagram/images/generate-ai": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "AI 画像生成 (Cloudflare Workers AI / Flux Schnell) → R2 保存",
        "description": "### 用途\nカバースライドの背景画像など、単一スロットに対して AI 画像を 1 枚生成する。\nCloudflare Workers AI の Flux Schnell モデルで生成し、R2 にアップして永続 URL を返す。\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- size は square (1024x1024) / portrait (1024x1792) / landscape (1792x1024)\n- R2 キーは `posts/<campaign_code or 'shared'>/ai-gen/<timestamp>_<hash>.png`\n- 公開 URL は `/cdn/ig/<key>` で返る\n- `INSTAGRAM_R2` / `AI` binding が無いと 502\n- Flux Schnell は 4 step で高速 (体感 5-10 秒)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "prompt": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 2000
                  },
                  "campaign_code": {
                    "type": "string"
                  },
                  "size": {
                    "type": "string",
                    "enum": [
                      "square",
                      "portrait",
                      "landscape"
                    ]
                  }
                },
                "required": [
                  "prompt"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "生成完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "public_url": {
                      "type": "string"
                    },
                    "r2_key": {
                      "type": "string"
                    },
                    "prompt": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "public_url",
                    "r2_key",
                    "prompt"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "bad_gateway",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramImagesGenerateAiCreate"
      }
    },
    "/v1/admin/instagram/slides/{id}/generate-content": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドのコンテンツをLLMで自動生成 / Generate Slide Content with LLM",
        "description": "### 用途\nスライドのテンプレート `slot_schema`（キーと型定義）を LLM に渡し、参考素材から各スロットの\nテキスト / 画像 URL / 数値を自動生成する。既存 `content` はマージで保持。\n\n### 管理者ポータルでの使用タイミング\n- スライドエディタの「AI に書かせる」ボタン\n- 特定フィールドのみ再生成（`target_keys` 指定）\n- 新規スライド作成直後の初期埋め\n\n### 認証\n要 `requireAdmin`。LLM 消費あり。\n\n### 挙動・制約\n- 素材: `source_material` 優先、未指定なら `campaign.source_material` → `campaign.notes` の順でフォールバック\n- `persist_source=true` で campaign に素材を保存（次回デフォルト化）\n- プロバイダ: `ai_providers` から取得、`provider_id` / `model` で明示指定可\n- `ai_usage_logs` に使用量が記録される\n\n### 関連\n- `POST /v1/admin/instagram/slides/{id}/revise` — 既存 content を指示に沿ってリバイズ\n- `POST /v1/admin/instagram/posts/{id}/generate-all` — 全スライド一括生成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "source_material": {
                    "type": "string"
                  },
                  "persist_source": {
                    "type": "boolean"
                  },
                  "hint": {
                    "type": "string"
                  },
                  "target_keys": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "生成された content（対象キーのみ。既存値は保持してマージ）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "content": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "content"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesGenerateContentCreate"
      }
    },
    "/v1/admin/instagram/slides/{id}/revise": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "スライドHTMLをLLMで修正 / Revise Slide HTML with LLM",
        "description": "### 用途\nスライドの HTML（`html_override` または テンプレ `html_body`）を、\n自然言語指示 (`instructions`) に従って LLM が修正する。結果は `html_override` に保存され、\n`revision_notes` に指示内容が記録される。\n\n### 管理者ポータルでの使用タイミング\n- スライドエディタ「AI に修正させる」パネル\n- 「もう少し派手に」「ブランドカラーを強調」などの軽微な調整\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- システムプロンプトで「修正後 HTML のみ」を要求、コードフェンスは除去\n- `html_override` に保存 → テンプレ `html_body` より優先される\n- `ai_usage_logs` に使用量記録\n- 元に戻したい場合は PATCH で `html_override: null` を送る\n\n### 関連\n- `PATCH /v1/admin/instagram/slides/{id}` — html_override の手動編集・クリア",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instructions": {
                    "type": "string",
                    "minLength": 1
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                },
                "required": [
                  "instructions"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "修正済みHTML",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "html": {
                      "type": "string"
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "input_tokens": {
                          "type": "number"
                        },
                        "output_tokens": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "input_tokens",
                        "output_tokens"
                      ]
                    }
                  },
                  "required": [
                    "html",
                    "usage"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramSlidesReviseCreate"
      }
    },
    "/v1/admin/instagram/images/detect-sensitive": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "画像から敏感情報領域を検出 / Detect Sensitive Areas in Image",
        "description": "### 用途\n画像 URL を Cloudflare Workers AI に投げ、**顔 / ナンバープレート候補領域**\nのバウンディングボックスを返す。モザイク / ぼかし処理の初期候補として利用。\n\n### 管理者ポータルでの使用タイミング\n- 画像を投稿に取り込むとき、センシティブ領域を事前検出して編集画面に反映\n- モザイクエディタの「自動検出」ボタン\n\n### 認証\n要 `requireAdmin`。Workers AI 消費あり（`env.AI` binding が必要）。\n\n### 挙動・制約\n- Workers AI は専用の顔 / プレート検出モデルを持たないため、\n  `@cf/facebook/detr-resnet-50`（汎用物体検出）で person / car / truck / bus を検出し、\n  person box の上部 30% を **顔候補**、車の下部 15% 中央を **プレート候補** として返す\n- ヒューリスティック初期候補なので、管理者が**必ず手動で確認・修正**する前提\n- `min_score` で DETR 信頼度フィルタ（既定 0.5）\n- `image_width/height` 指定時は正規化した座標を返す\n\n### 関連\n- `POST /v1/admin/instagram/upload-image` — 検出対象の画像アップロード",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "image_width": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "image_height": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "min_score": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  }
                },
                "required": [
                  "image_url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "検出された候補領域（相対座標 0-1）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "boxes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "label": {
                            "type": "string",
                            "enum": [
                              "face",
                              "plate"
                            ]
                          },
                          "x": {
                            "type": "number"
                          },
                          "y": {
                            "type": "number"
                          },
                          "width": {
                            "type": "number"
                          },
                          "height": {
                            "type": "number"
                          },
                          "score": {
                            "type": "number"
                          },
                          "source": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "label",
                          "x",
                          "y",
                          "width",
                          "height",
                          "score",
                          "source"
                        ]
                      }
                    }
                  },
                  "required": [
                    "boxes"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramImagesDetectSensitiveCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}/generate-all": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "テンプレートから投稿をAIで組み立て / Generate Full Post with AI",
        "description": "### 用途\nキャンペーンに対して、投稿テンプレート（`post_template_id`）またはテンプレ ID 配列\n（`template_ids`）を元に、複数スライド + content + キャプションを **AI がまとめて生成する**\nオールインワン生成エンドポイント。\n\n### 管理者ポータルでの使用タイミング\n- 新規キャンペーン作成後の「AI で一括生成」ボタン\n- テンプレ変更時の全面再生成\n\n### 認証\n要 `requireAdmin`。LLM 消費量が大きい操作。\n\n### 挙動・制約\n- `spot_count` で生成するスライド枚数を制御（0〜10、既定 5）\n- 既存スライドは削除してから再生成（トランザクション）\n- `ai_usage_logs` に生成量・コストが記録される\n- 失敗時は途中まで生成されたスライドが残る可能性あり（呼出し側でリセット推奨）\n- `skip_ai: true` を指定すると LLM 呼出をスキップし content=空のスライドだけ作成する。\n  「空の投稿を作成 + 投稿テンプレ適用」モード用途で利用。\n\n### 関連\n- `POST /v1/admin/instagram/slides/{id}/generate-content` — 個別スライド生成\n- `POST /v1/admin/instagram/posts/{id}/generate-from-parking-lots` — 駐車場ベース生成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "post_template_id": {
                    "type": "string"
                  },
                  "template_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "spot_count": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10
                  },
                  "hint": {
                    "type": "string"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  },
                  "skip_ai": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created_slides": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "created_slides"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsGenerateAllCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}/generate-from-parking-lots": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "駐車場から投稿を一括生成 / Generate Posts from Parking Lots",
        "description": "### 用途\n駐車場 ID 配列（最大 10 件）から、駐車場情報を Parky Postgres から引いて、\n投稿テンプレートの各スライドに **決定論的にマッピング** して content + PNG 指示を生成する。\n「スポット紹介投稿」を短時間で量産するための専用導線。\n\n### 管理者ポータルでの使用タイミング\n- 駐車場ランキング記事の投稿化\n- 特集エリアの駐車場をピックアップしてスライド化\n- カバー + ランキング + 詳細スライド群を一気に作る\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `parking_lot_ids` は 1〜10 件\n- 駐車場情報（name / address / images / rating / tags 等）を Postgres から bulk fetch\n- ランキング系テンプレは順序も決定論的（配列の並び順がそのまま順位）\n- 既存スライドは削除してから生成\n- 画像は R2 既存参照を流用（新規アップロードは不要）\n\n### 関連\n- `POST /v1/admin/instagram/posts/{id}/generate-all` — テンプレだけから汎用生成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parking_lot_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "post_template_id": {
                    "type": "string"
                  },
                  "cover_title": {
                    "type": "string"
                  },
                  "cover_area": {
                    "type": "string"
                  },
                  "hint": {
                    "type": "string"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                },
                "required": [
                  "parking_lot_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成完了",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created_slides": {
                      "type": "number"
                    },
                    "slide_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "created_slides",
                    "slide_ids"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsGenerateFromParkingLotsCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}/generate-caption": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャプションをLLMで生成 / Generate Caption with LLM",
        "description": "### 用途\nキャンペーンのタイトル・エリア・スライド content 要約を元に、Instagram 投稿用の\nキャプション本文と推奨ハッシュタグ配列を LLM に生成させる。\n\n### 管理者ポータルでの使用タイミング\n- 投稿編集画面の「AI でキャプション作成」ボタン\n- スライド編集後にキャプションを再生成する導線\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- スライド全件の `content` を収集してコンテキスト化\n- 出力: `{ caption: string, hashtags: string[] }`\n- DB への保存はしない（`PATCH /posts/{id}/caption` で明示的に保存）\n- `ai_usage_logs` に使用量記録\n\n### 関連\n- `PATCH /v1/admin/instagram/posts/{id}/caption` — 生成結果を保存\n- `POST /v1/admin/instagram/posts/{id}/generate-ideas` — タイトル案生成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hint": {
                    "type": "string"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "生成されたキャプション",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "caption": {
                      "type": "string"
                    },
                    "hashtags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "caption",
                    "hashtags"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsGenerateCaptionCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}/caption": {
      "patch": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "キャプションを手動更新 / Update Caption Manually",
        "description": "### 用途\nキャンペーンの `ig_captions`（キャプション本文 / ハッシュタグ）を upsert する。\nAI 生成結果の保存、または手動編集内容の反映に使う。\n\n### 管理者ポータルでの使用タイミング\n- 投稿編集画面のキャプションエディタ「保存」\n- `/generate-caption` 生成結果の「この内容で保存」採用時\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `INSERT ... ON CONFLICT(campaign_id) DO UPDATE` で upsert\n- `body` / `hashtags` いずれか未指定なら既存値を COALESCE で保持\n- 1 キャンペーンにつき 1 行（UNIQUE 制約）\n\n### 関連\n- `POST /v1/admin/instagram/posts/{id}/generate-caption` — AI 生成（保存しない）",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string"
                  },
                  "hashtags": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/IgCaption"
                    },
                    {
                      "type": "object"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsCaptionUpdate"
      }
    },
    "/v1/admin/instagram/posts/{id}/generate-ideas": {
      "post": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "競合分析とアイデア生成 / Generate Competitor Analysis & Ideas",
        "description": "### 用途\n競合アカウントの投稿メモや参考 URL から、Parky の Instagram コンテンツ企画案\n（タイトル / コンセプト / フック）を 5〜8 件 LLM に提案させる企画ブレスト用エンドポイント。\n\n### 管理者ポータルでの使用タイミング\n- 新規企画検討画面の「AI にアイデア出しさせる」ボタン\n- 他アカウント（`account_handle`）の URL を貼って類似企画を量産\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `notes` 必須、`area`・`source_url`・`account_handle` 任意\n- 出力は `{ ideas: [{ title, concept, hook }, ...] }`（常に JSON 配列のみ）\n- DB 保存なし（管理者が選んで `POST /v1/admin/instagram/posts` で新規作成する想定）\n- `ai_usage_logs` に使用量記録\n\n### 関連\n- `POST /v1/admin/instagram/posts` — 選んだアイデアでキャンペーン作成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "notes": {
                    "type": "string",
                    "minLength": 1
                  },
                  "area": {
                    "type": "string"
                  },
                  "source_url": {
                    "type": "string"
                  },
                  "account_handle": {
                    "type": "string"
                  },
                  "provider_id": {
                    "type": "string"
                  },
                  "model": {
                    "type": "string"
                  }
                },
                "required": [
                  "notes"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "生成されたアイデア一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ideas": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "concept": {
                            "type": "string"
                          },
                          "hook": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "title",
                          "concept",
                          "hook"
                        ]
                      }
                    }
                  },
                  "required": [
                    "ideas"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsGenerateIdeasCreate"
      }
    },
    "/v1/admin/instagram/posts/{id}/snapshots": {
      "get": {
        "tags": [
          "Instagram / Instagram"
        ],
        "summary": "競合分析スナップショット一覧 / Competitor Analysis Snapshots",
        "description": "### 用途\nキャンペーンに紐づく **競合分析スナップショット** (`ig_competitor_snapshots`) 一覧を返す。\n`/generate-ideas` で生成したアイデアと、その時参照した競合 URL / メモを履歴として保存する。\n\n### 管理者ポータルでの使用タイミング\n- 企画タブの「過去の競合分析」セクション\n- アイデア生成履歴の参照\n\n### 認証\n要 `requireAdmin`。\n\n### 挙動・制約\n- `created_at DESC` 降順（新しいスナップショットが上）\n- スナップショットは `/generate-ideas` 実行時に自動で保存される\n\n### 関連\n- `POST /v1/admin/instagram/posts/{id}/generate-ideas` — 新規スナップショット生成",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IgCompetitorSnapshot"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminInstagramPostsSnapshotsList"
      }
    },
    "/v1/admin/assets": {
      "get": {
        "tags": [
          "アセット / Assets"
        ],
        "summary": "アセット一覧 / Asset List",
        "description": "### 用途\nCloudflare R2 にアップロード済みアセット（`assets`）のメタデータ一覧を返す。\n`s3_key` は R2 バケット内のオブジェクトキー。\n\n### 管理者ポータルでの使用タイミング\n- システム > アセット管理画面のグリッド表示\n- バッジ定義 / 通知 / コンテンツでアイコン選択ピッカーを開いたとき\n- カテゴリ別タブ（badge_icon / lot_photo / banner 等）切替時\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 並び順: `created_at DESC`（新着優先）\n- `category` 未指定時は全カテゴリ返却\n- ページングは `PageQuerySchema` 準拠（page / limit）\n- レスポンスにはメタデータのみで、画像本体 URL は含まれない（別途 R2 公開 URL 構築）\n\n### 関連\n- `POST /v1/admin/assets/` — メタデータ作成\n- `GET /v1/admin/assets/{id}` — 詳細",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminAsset"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAssetsList"
      },
      "post": {
        "tags": [
          "アセット / Assets"
        ],
        "summary": "アセットを作成 / Create Asset",
        "description": "### 用途\nファイル本体は事前に R2 へアップロード済みである前提で、その**メタデータ行**を\n`assets` テーブルに作成する。これによりバッジ・通知・コンテンツから `asset_id` で参照可能になる。\n\n### 管理者ポータルでの使用タイミング\n- アセット管理画面のアップロードフォームで R2 PUT 完了後の登録ステップ\n- バッジ作成モーダル内の「画像アップロード」フロー終端\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- `s3_key` は R2 オブジェクトキー（重複は UNIQUE 制約で 409）\n- `width` / `height` は画像の場合のみ任意指定\n- `file_size` はバイト数\n- 監査ログ `asset.create` をファイル名・カテゴリ・サイズ付きで記録\n\n### 関連\n- `PATCH /v1/admin/assets/{id}` — メタデータ修正\n- `DELETE /v1/admin/assets/{id}` — DB 行削除（R2 本体は別途）",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "category": {
                    "type": "string"
                  },
                  "s3_key": {
                    "type": "string"
                  },
                  "file_name": {
                    "type": "string"
                  },
                  "mime_type": {
                    "type": "string"
                  },
                  "file_size": {
                    "type": "integer"
                  },
                  "width": {
                    "type": "integer"
                  },
                  "height": {
                    "type": "integer"
                  }
                },
                "required": [
                  "category",
                  "s3_key",
                  "file_name",
                  "mime_type",
                  "file_size"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAsset"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAssetsCreate"
      }
    },
    "/v1/admin/assets/{id}": {
      "get": {
        "tags": [
          "アセット / Assets"
        ],
        "summary": "アセット詳細 / Asset Detail",
        "description": "### 用途\nアセット 1 件の詳細メタデータを返す。一覧で表示しきれない情報や、編集モーダルでの\n現在値表示に使う。\n\n### 管理者ポータルでの使用タイミング\n- アセット詳細モーダルを開いたとき\n- リンク経由で直接アセット詳細にアクセスしたとき\n- 編集前に最新値を取り直すとき\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動・制約\n- 明示列を SELECT して 1 件返却（id / category / s3_key / file_name / mime_type / file_size / created_at）\n- 該当 ID なしは 404 `not_found`\n- ファイル本体（R2 オブジェクト）は別途取得\n\n### 関連\n- `PATCH /v1/admin/assets/{id}` — 更新\n- `DELETE /v1/admin/assets/{id}` — 削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAsset"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAssetsGet"
      },
      "patch": {
        "tags": [
          "アセット / Assets"
        ],
        "summary": "アセットを更新 / Update Asset",
        "description": "### 用途\nR2 アセットのメタデータ（カテゴリ・ファイル名・MIME type 等）を部分更新する。\nファイル実体 (R2 上のオブジェクト) は触らず、`assets` テーブルのメタだけを書き換える。\n\n### 管理者ポータルでの使用タイミング\n- アセット管理画面でのファイル名リネーム\n- カテゴリ再分類（avatar / review / theme / badge 等）\n- 管理者タグの付け替え\n\n### 認証・認可\n要 `requireAdmin`。`assets:write` 権限が必要。\n\n### 挙動・制約\n- ボディは passthrough で柔軟に受け、そのまま UPDATE に流す\n- 空ボディの場合は現在値を返す（冪等）\n- 存在しない ID は `not_found` (404)\n- `admin_activity_logs` に `asset.update` と diff を記録\n- R2 本体は変更しない（移動・差し替えは別フロー）\n\n### 関連\n- `POST /v1/admin/assets` — 作成（メタデータのみ）\n- `DELETE /v1/admin/assets/{id}` — 削除\n- `POST /v1/storage/upload-url` — R2 への実ファイルアップロード URL 発行",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminAssetUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAsset"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAssetsUpdate"
      },
      "delete": {
        "tags": [
          "アセット / Assets"
        ],
        "summary": "アセットを削除 / Delete Asset",
        "description": "### 用途\n`assets` テーブルからアセットメタデータを物理削除する。R2 上のバイナリ本体は\n**別途 GC ジョブで掃除**する方式（即時削除すると他エンティティからの参照切れを\n引き起こすため）。\n\n### 管理者ポータルでの使用タイミング\n- アセット管理画面の「削除」ボタン押下\n- 孤立アセットの一括削除スクリプト\n\n### 認証・認可\n要 `requireAdmin`。`assets:delete` 権限が必要。\n\n### 挙動・制約\n- `DELETE FROM assets WHERE id = ?` の物理削除\n- 削除前に `file_name` を取得し、`admin_activity_logs` に `asset.delete` として記録\n- R2 オブジェクトは別ジョブがクリーンアップ（`s3_key` の参照が 0 件になったタイミング）\n- 存在しない ID でも 204（冪等）\n\n### 関連\n- `PATCH /v1/admin/assets/{id}` — メタデータ更新\n- `GET /v1/admin/assets` — 一覧",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAssetsDelete"
      }
    },
    "/v1/admin/user-vehicles": {
      "get": {
        "tags": [
          "ユーザー車両 / User Vehicles"
        ],
        "summary": "全ユーザー車両一覧 / All User Vehicle List",
        "description": "### 用途\n全ユーザーの登録車両を管理者が横断閲覧する。所有者（`app_users`）と車両画像\n（`assets`）を LEFT JOIN し、ネスト構造で返す。`user_id` 指定で特定ユーザーの車両に絞り込み可能。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー管理 → ユーザー詳細「登録車両」タブ表示時\n- サポート対応で「どんな車両を停めようとしているか」を確認する時\n- 車両モデル名・ニックネームでの横断検索（`q` パラメータ ILIKE）\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `deleted_at IS NULL` のみ返却（ソフトデリート済みは除外）\n- ページネーション対応（`PageQuerySchema`）\n- `q` は `model_name` / `nickname` の ILIKE 検索\n- `created_at DESC` で降順\n\n### 関連\n- `GET /v1/admin/user-vehicles/{id}` — 単一取得\n- `POST /v1/admin/user-vehicles` — 管理者代理で新規登録",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUserVehicle"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserVehiclesList"
      },
      "post": {
        "tags": [
          "ユーザー車両 / User Vehicles"
        ],
        "summary": "車両を登録（管理者代理）/ Register Vehicle (Admin Proxy)",
        "description": "### 用途\n管理者がユーザーの代理で車両レコードを作成する。サポート対応で「ユーザーが車両を\nうまく登録できない」「アプリ側のバグで登録が失敗した」などのケースに利用。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細「登録車両」タブの「追加」ボタン\n- サポートチケット起点の代理登録ワークフロー\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `is_primary=true` で作成時、同ユーザーの既存主登録を自動解除（部分 UNIQUE 対応）\n- `vehicle_type_code` / `maker_code` 等のコードマスタ値は DB 側 CHECK / FK で検証\n- `passthrough()` でフィールドを受けるため将来カラム追加に追従可能\n- 成功時に `admin_activity_logs` へ `user_vehicle.create` を記録\n\n### 関連\n- `PATCH /v1/admin/user-vehicles/{id}` — 更新\n- `DELETE /v1/admin/user-vehicles/{id}` — ソフトデリート",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "nickname": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "vehicle_type_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "maker_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "size_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "color_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "model_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "year": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "asset_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "is_primary": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "deleted_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "user_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserVehicle"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserVehiclesCreate"
      }
    },
    "/v1/admin/user-vehicles/{id}": {
      "get": {
        "tags": [
          "ユーザー車両 / User Vehicles"
        ],
        "summary": "車両詳細 / Vehicle Detail",
        "description": "### 用途\n車両 1 件を所有者・車両画像 join 付きで取得する。編集モーダルや詳細パネルの\n現在値表示に使う。ソフトデリート済みのレコードも返す（履歴確認のため）。\n\n### 管理者ポータルでの使用タイミング\n- 車両一覧の行クリックで開く詳細パネル\n- 編集モーダル起動時(最新値の取り直し)\n- 通報・問い合わせから車両 ID で直接遷移したとき\n\n### 認証・認可\n`requireAdmin`(管理者ロール必須)。\n\n### 挙動・制約\n- 存在しない ID は 404 `not_found`\n- `deleted_at` がセットされていても返却される(一覧 API とは挙動が異なる)\n\n### 関連\n- `GET /v1/admin/user-vehicles` — 一覧\n- `PATCH /v1/admin/user-vehicles/{id}` — 部分更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "1 件",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserVehicle"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserVehiclesGet"
      },
      "patch": {
        "tags": [
          "ユーザー車両 / User Vehicles"
        ],
        "summary": "車両を更新 / Update Vehicle",
        "description": "### 用途\n車両レコードを部分更新する。送られたフィールドだけを上書きし、未指定カラムは現行値を維持。\n管理者代理での修正・モデル名訂正・主登録切替などに使う。\n\n### 管理者ポータルでの使用タイミング\n- 車両詳細パネルの編集モーダル「保存」\n- 「主登録に切替」トグル操作（`is_primary=true`）\n- 通報対応で nickname / notes を訂正するとき\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `is_primary=true` への変更時、同ユーザーの他車両を自動で `is_primary=false` に落とす（トランザクション）\n- 空ボディ POST は現在値をそのまま返す（no-op）\n- 存在しない ID は 404 `not_found`\n- 成功時に `admin_activity_logs` へ `user_vehicle.update`（diff 含む）を記録\n\n### 関連\n- `POST /v1/admin/user-vehicles` — 新規登録\n- `DELETE /v1/admin/user-vehicles/{id}` — ソフトデリート",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserVehicleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserVehicle"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserVehiclesUpdate"
      },
      "delete": {
        "tags": [
          "ユーザー車両 / User Vehicles"
        ],
        "summary": "車両をソフト削除 / Soft Delete Vehicle",
        "description": "### 用途\n車両レコードを論理削除（`deleted_at = NOW()`）する。物理削除はしない。\nユーザーが削除を依頼した・スパム車両を停止する・誤登録の取消などに使う。\n\n### 管理者ポータルでの使用タイミング\n- 車両詳細パネルの「削除」ボタン\n- 一覧での行スワイプ削除アクション\n- サポート対応で「ユーザーが削除できなかった車両」の代理削除\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `deleted_at = NOW()` を立てるソフトデリート（物理削除はしない）\n- 一覧 API からは以降返らなくなるが、単一取得 (`GET /{id}`) では引き続き取得可能\n- 存在しない ID でも 204（冪等）\n- 成功時に `admin_activity_logs` へ `user_vehicle.delete`（`soft: true`）を記録\n\n### 関連\n- `PATCH /v1/admin/user-vehicles/{id}` — 更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserVehiclesDelete"
      }
    },
    "/v1/admin/user-search-presets": {
      "get": {
        "tags": [
          "検索プリセット（ユーザー）/ User Search Presets"
        ],
        "summary": "検索プリセット一覧 / Search Preset List",
        "description": "### 用途\nユーザーが保存している検索プリセット（`user_search_presets`）を管理者視点で閲覧する。\nプリセットは自宅周辺座標を含み得る個人情報のため、`user_id` 指定時は閲覧ログを記録する。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細「検索プリセット」タブ表示時（`user_id` 指定）\n- サポート問い合わせで「保存条件がおかしい」を再現する時\n- 横断的な保存条件傾向の分析（`user_id` 未指定）\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `deleted_at IS NULL` のみ返却\n- 並び順: `user_id, sort_order ASC, created_at ASC`\n- `user_id` 指定時のみ `admin_activity_logs` へ `user.view_search_presets` を記録（一覧ダンプはログ対象外）\n\n### 関連\n- `GET /v1/admin/user-search-presets/{id}` — 単一取得\n- `PATCH /v1/admin/user-search-presets/{id}` — 管理者代理更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUserSearchPreset"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserSearchPresetsList"
      },
      "post": {
        "tags": [
          "検索プリセット（ユーザー）/ User Search Presets"
        ],
        "summary": "プリセットを追加（管理者代理）/ Add Preset (Admin Proxy)",
        "description": "### 用途\n管理者が指定ユーザーの代理で検索プリセットを 1 件追加する。\nサポート対応で「ユーザーがプリセット保存に失敗した」「キャンペーン条件を一斉配布したい」\nなどのケースで使用。\n\n### 管理者ポータルでの使用タイミング\n- ユーザー詳細「検索プリセット」タブの「追加」ボタン\n- サポートチケット起点の代理登録\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `name` 1〜50 文字 / `query_json` は `SearchQueryV1` 準拠\n- `is_default=true` で作成すると、同ユーザーの既存デフォルトを自動解除（トランザクション）\n- 1 ユーザー 20 件上限。超過は `P0010` トリガから 400 `bad_request` へ写像\n- 成功時に `admin_activity_logs` へ `user.create_search_preset`（query_json 含む）を記録\n\n### 関連\n- `PATCH /v1/admin/user-search-presets/{id}` — 部分更新\n- `POST /v1/admin/user-search-presets/{id}/set-default` — デフォルト切替",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "query_json": {
                    "$ref": "#/components/schemas/SearchQueryV1"
                  },
                  "is_default": {
                    "type": "boolean"
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                },
                "required": [
                  "user_id",
                  "name",
                  "query_json"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserSearchPreset"
                }
              }
            }
          },
          "400": {
            "description": "入力不正 or 件数上限",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserSearchPresetsCreate"
      }
    },
    "/v1/admin/user-search-presets/{id}": {
      "get": {
        "tags": [
          "検索プリセット（ユーザー）/ User Search Presets"
        ],
        "summary": "プリセット詳細 / Preset Detail",
        "description": "### 用途\nプリセット 1 件を所有者情報込みで取得する。編集モーダルや詳細ビューの\n現在値を表示するために使う。\n\n### 管理者ポータルでの使用タイミング\n- プリセット行をクリックして詳細を開いた時\n- 編集モーダル起動時の最新値取り直し\n- サポート問い合わせで `query_json` の中身を確認する時\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- 存在しない / `deleted_at` がセット済みの場合 404 `not_found`\n- 所有者の `display_name` も一緒に返す（`app_users` JOIN）\n\n### 関連\n- `PATCH /v1/admin/user-search-presets/{id}` — 部分更新\n- `DELETE /v1/admin/user-search-presets/{id}` — ソフトデリート",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "1 件",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserSearchPreset"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "存在しない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserSearchPresetsGet"
      },
      "patch": {
        "tags": [
          "検索プリセット（ユーザー）/ User Search Presets"
        ],
        "summary": "プリセットを更新（管理者代理）/ Update Preset (Admin Proxy)",
        "description": "### 用途\nプリセットの名前・検索条件・並び順を管理者代理で部分更新する。\n送られたフィールドのみ COALESCE で上書きし、未指定カラムは現行値維持。\n\n### 管理者ポータルでの使用タイミング\n- プリセット編集モーダルの「保存」\n- サポート対応で誤った保存条件を訂正する時\n- プリセット並び順のドラッグ&ドロップ確定時\n\n### 認証・認可\n`requireAdmin`（管理者ロール必須）。\n\n### 挙動・制約\n- `is_default` はこのエンドポイントでは変更しない（専用 `set-default` を使用）\n- 存在しない / 削除済みの場合 404 `not_found`\n- 成功時に `admin_activity_logs` へ `user.update_search_preset`（before/after diff）を記録\n\n### 関連\n- `POST /v1/admin/user-search-presets/{id}/set-default` — デフォルト切替\n- `DELETE /v1/admin/user-search-presets/{id}` — ソフトデリート",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "query_json": {
                    "$ref": "#/components/schemas/SearchQueryV1"
                  },
                  "sort_order": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserSearchPreset"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "存在しない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserSearchPresetsUpdate"
      },
      "delete": {
        "tags": [
          "検索プリセット（ユーザー）/ User Search Presets"
        ],
        "summary": "プリセットをソフト削除（管理者代理）/ Soft Delete Preset (Admin Proxy)",
        "description": "### 用途\nプリセットを論理削除（`deleted_at = NOW()`）する。物理削除はしない。\nユーザー本人の削除依頼やコンプライアンス対応で利用。\n\n### 管理者ポータルでの使用タイミング\n- プリセット詳細パネルの「削除」ボタン\n- ユーザー退会時の付随データ整理\n\n### 認証・認可\n`requireAdmin`(管理者ロール必須)。\n\n### 挙動・制約\n- ソフトデリート(`deleted_at = NOW()`)。物理削除はしない\n- 削除対象が `is_default=true` だった場合、`is_default=false` に同時に落とす\n- 存在しない / 既に削除済みの場合 404 `not_found`\n- 成功時に `admin_activity_logs` へ `user.delete_search_preset`(query_json スナップショット含む)を記録\n\n### 関連\n- `PATCH /v1/admin/user-search-presets/{id}` — 更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserSearchPresetsDelete"
      }
    },
    "/v1/admin/user-search-presets/{id}/set-default": {
      "post": {
        "tags": [
          "検索プリセット（ユーザー）/ User Search Presets"
        ],
        "summary": "プリセットをデフォルトに設定（管理者代理）/ Set Default Preset (Admin Proxy)",
        "description": "### 用途\n指定プリセットを唯一のデフォルト（`is_default=true`）にする。同ユーザーの他プリセットは\nアトミックに `is_default=false` へ切り替わる。\n\n### 管理者ポータルでの使用タイミング\n- プリセット詳細パネルの「デフォルトに設定」\n- プリセット一覧での ⭐ アイコンクリック\n\n### 認証・認可\n`requireAdmin`(管理者ロール必須)。\n\n### 挙動・制約\n- 単一 UPDATE で同ユーザー内の `is_default` を一括切替（アトミック）\n- 既にデフォルトのプリセットに対しても 200（現在値）を返す（冪等）\n- 存在しない / 削除済みの場合 404 `not_found`\n- 成功時に `admin_activity_logs` へ `user.set_default_search_preset` を記録\n\n### 関連\n- `PATCH /v1/admin/user-search-presets/{id}` — 名前・条件更新",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserSearchPreset"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "存在しない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminUserSearchPresetsSetDefaultCreate"
      }
    },
    "/v1/admin/places/import/lot/{id}": {
      "post": {
        "tags": [
          "周辺施設 / Places"
        ],
        "summary": "駐車場周辺施設を取込 / Import Nearby Places for Lot",
        "description": "### 用途\n指定駐車場の lat/lng を中心に Google Places API (Nearby Search) を叩き、\n取得した施設を `area_places` に `source='google_places'` で UPSERT する。\n\n### 管理者ポータルでの使用タイミング\n- 新規駐車場登録直後に 1 回\n- 周辺の施設が入れ替わったと疑われるとき手動で再実行\n\n### 挙動\n- Places API `searchNearby` で半径 `radius_m` (既定 1000m) / 最大 20 件取得\n- カテゴリ別デフォルトに従って `show_on_map` / `show_as_facility` を初期化\n- 既存行 (source='google_places' + source_place_id 一致) は基本情報のみ更新し、\n  `preserve_manual_flags=true` (既定) の場合は運営が手動調整した show_* を維持\n- `rating` / `user_ratings_total` / `place_types` / `last_refreshed_at` は常に最新に更新\n\n### コスト\n1 回あたり Places API Nearby Search Essentials = $0.032。\n無料枠 $200/月 で 6,250 回実行可能。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 100,
              "maximum": 5000,
              "default": 1000
            },
            "required": false,
            "name": "radius_m",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 20,
              "default": 5
            },
            "required": false,
            "name": "map_auto_limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 20,
              "default": 5
            },
            "required": false,
            "name": "facility_auto_limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "default": "true"
            },
            "required": false,
            "name": "preserve_manual_flags",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "取込結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlacesImportLotResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlacesImportLotCreate"
      }
    },
    "/v1/admin/places/import/all": {
      "post": {
        "tags": [
          "周辺施設 / Places"
        ],
        "summary": "全駐車場周辺施設を一括取込 / Import All Nearby Places",
        "description": "### 用途\n全 `parking_lots` (deleted_at IS NULL, lat/lng 有) に対して順次 Places 取込を実行。\n週次 cron の手動キック版として運用。\n\n### 挙動\n- `stale_days` (既定 7) より新しく取込済みの駐車場は skip\n- 1 リクエスト間に 1 秒の wait (Places API レート制限対応)\n- `dry_run=true` で対象件数と推定コストのみ返す\n\n### 注意\nWorkers の CPU/実行時間上限に注意。対象 >100 件のときは cron (週次) に任せ、\n手動キックは少数駐車場に絞った範囲で使用する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "default": "false"
            },
            "required": false,
            "name": "dry_run",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 365,
              "default": 7
            },
            "required": false,
            "name": "stale_days",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "取込結果サマリ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlacesImportAllResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlacesImportAllCreate"
      }
    },
    "/v1/admin/places": {
      "get": {
        "tags": [
          "周辺施設 / Places"
        ],
        "summary": "周辺施設一覧 / Place List",
        "description": "### 用途\n管理者ポータル「周辺施設管理」画面で、駐車場単位に area_places を表示する。\n\n### フィルタ\n- `lot_id` — 指定駐車場の半径 2km 以内の施設のみ (PostGIS)\n- `is_sponsored` / `source` / `category`\n- `show_on_map` / `show_as_facility` — フラグで絞る\n- `include_deleted` — ソフトデリート済も含める",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "is_sponsored",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "category",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "show_on_map",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "show_as_facility",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminAreaPlace"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "items",
                    "total",
                    "page",
                    "limit"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlacesList"
      },
      "post": {
        "tags": [
          "周辺施設 / Places"
        ],
        "summary": "施設を手動追加 / Add Place Manually",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "category": {
                    "type": "string",
                    "default": "other"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "logo_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "banner_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "thumbnail_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "link_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "lat": {
                    "type": "number"
                  },
                  "lng": {
                    "type": "number"
                  },
                  "radius_m": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 5000,
                    "default": 500
                  },
                  "show_on_map": {
                    "type": "boolean",
                    "default": false
                  },
                  "show_as_facility": {
                    "type": "boolean",
                    "default": true
                  },
                  "place_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "opening_hours": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "memo": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "name",
                  "lat",
                  "lng"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAreaPlace"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlacesCreate"
      }
    },
    "/v1/admin/places/{id}": {
      "patch": {
        "tags": [
          "周辺施設 / Places"
        ],
        "summary": "施設を更新 / Update Place",
        "description": "ダッシュボードで `show_on_map` / `show_as_facility` / `category` 等を調整する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "category": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "logo_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "banner_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "thumbnail_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "link_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "lat": {
                    "type": "number"
                  },
                  "lng": {
                    "type": "number"
                  },
                  "radius_m": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 5000
                  },
                  "show_on_map": {
                    "type": "boolean"
                  },
                  "show_as_facility": {
                    "type": "boolean"
                  },
                  "place_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "opening_hours": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "memo": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "type": "string"
                  },
                  "is_sponsored": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAreaPlace"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlacesUpdate"
      },
      "delete": {
        "tags": [
          "周辺施設 / Places"
        ],
        "summary": "施設をソフト削除 / Soft Delete Place",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除済み"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlacesDelete"
      }
    },
    "/v1/admin/osm-ingest": {
      "post": {
        "tags": [
          "admin",
          "osm"
        ],
        "summary": "OSM Overpass amenity=parking 取り込みを bbox 指定で即時実行",
        "description": "### 用途\nPhase 1 C1 で導入した OSM 取り込み batch を admin から one-shot で発火する。\ncron (`OSM_DAILY` 0 16 * * *) を待たずに、bbox を試行錯誤しながら品質確認する想定。\n\n### 挙動\n- body の `bbox` を渡せば優先、省略時は `app_config.cron.osm_ingest_bbox` を読む\n- `dry_run=true` で bbox 確認のみ (Overpass / DB 書込み発生せず)\n- 既存 lot に半径 50m 以内でマッチした elements のみ field_values に書き込む\n- マッチしない raw payload は `parking_ingest_payloads` に蓄積、parking_lots 自動生成なし\n\n### 推奨運用\n1. 東京 23 区 (south=35.55, west=139.55, north=35.85, east=139.95) で初回試走\n2. summary を見て matched / unmatched / fields_inserted を確認\n3. 良ければ広域に bbox 拡張、または app_config に保存して cron 化\n\n### 制約\nWorker CPU 30s を踏まえ、bbox 内 elements は数百以下を推奨\n(東京 23 区で ~500、関東全域で 数千 → タイムアウトの可能性)。",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminOsmIngestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "取込結果サマリ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminOsmIngestResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminOsmIngestCreate"
      }
    },
    "/v1/admin/google-ingest": {
      "post": {
        "tags": [
          "admin",
          "google"
        ],
        "summary": "Google Places (New) Nearby Search で parking 取り込みを bbox 指定で即時実行",
        "description": "### 用途\nPhase 1 で導入した Google Places 取り込み batch を admin から one-shot で発火する。\ncron / PR 2 の HOURLY ingest_schedules を待たずに bbox を試行錯誤しながら品質確認する想定。\n\n### 挙動\n- body の `bbox` を渡せば優先、省略時は `app_config.cron.google_ingest_bbox` を読む\n- `dry_run=true` で bbox 確認のみ (Places API / DB 書込み発生せず)\n- bbox を 2km grid に分割し、各 grid 中心で Nearby Search (radius 1500m) を叩く\n- 既存 lot に半径 50m 以内でマッチした places のみ field_values に書き込む\n- マッチしない raw payload は `parking_ingest_payloads` に蓄積、parking_lots 自動生成なし\n\n### コスト\n- 1 grid セル = 1 Nearby Search req = $0.032 (Essentials)\n- `max_api_calls` (default 50) で req 数上限を必ず制御する\n\n### 制約\nWorker CPU 30s と grid セル間 wait (200ms) を踏まえ、\nmax_api_calls は 100 以下を推奨。env.GOOGLE_PLACES_API_KEY 未設定時は graceful no-op。",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminGoogleIngestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "取込結果サマリ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminGoogleIngestResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminGoogleIngestCreate"
      }
    },
    "/v1/admin/parking-moderation/summary": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "モデレーションの未処理件数サマリ (タブのバッジ用)",
        "responses": {
          "200": {
            "description": "未処理件数",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationSummary"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationSummary"
      }
    },
    "/v1/admin/parking-moderation/lots/pending": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "pending 駐車場一覧 (自動取り込み由来)",
        "description": "Phase C: status='pending' AND source != 'owner' の lot を返す。\nowner 申請は /v1/admin/parking-applications で扱うため除外。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "カンマ区切りの source filter (例: osm,google)"
            },
            "required": false,
            "name": "sources",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "pending 一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationPendingLotsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationLotsPendingList"
      }
    },
    "/v1/admin/parking-moderation/lots/{id}/approve": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "pending 駐車場を承認 (status='active')",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationRejectBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "承認結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationApproveResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "pending lot not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationLotsApprove"
      }
    },
    "/v1/admin/parking-moderation/lots/{id}/reject": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "pending 駐車場を却下 (status='rejected')",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationRejectBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "却下結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "pending lot not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationLotsReject"
      }
    },
    "/v1/admin/parking-moderation/lots/bulk-approve": {
      "patch": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "pending 駐車場を一括承認",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationBulkBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "一括承認結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationApproveResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationLotsBulkApproveUpdate"
      }
    },
    "/v1/admin/parking-moderation/lots/bulk-reject": {
      "patch": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "pending 駐車場を一括却下",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationBulkBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "一括却下結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationLotsBulkRejectUpdate"
      }
    },
    "/v1/admin/parking-moderation/field-values/pending": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "pending フィールド値一覧 (approved_at IS NULL)",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "sources",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "parking_lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "field_name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "min_confirms_n",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "pending 一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationPendingFieldValuesList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationFieldValuesPendingList"
      }
    },
    "/v1/admin/parking-moderation/field-values/{id}/approve": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "フィールド値を承認 (approved_at = now())",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "承認結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "pending field_value not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationFieldValuesApprove"
      }
    },
    "/v1/admin/parking-moderation/field-values/bulk-approve": {
      "patch": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "項目値を一括承認 (駐車場単位のまとめ承認用)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationFieldValueBulkApproveBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "一括承認結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationFieldValuesBulkApproveUpdate"
      }
    },
    "/v1/admin/parking-moderation/field-values/ugc-candidates": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "UGC 候補 (confirms_n >= N) + 現 primary との差分",
        "description": "Phase C-3 ダッシュボード用。Phase D で蓄積した confirms_n が閾値以上の UGC 候補と、同 lot + 同 field の現 primary 値を JOIN して返す。admin が比較レビュー可能。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "examples": [
                "3"
              ]
            },
            "required": false,
            "name": "min_confirms_n",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "UGC 候補 + 現 primary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationUgcCandidatesList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationFieldValuesUgcCandidatesList"
      }
    },
    "/v1/admin/parking-moderation/field-values/{id}/promote-to-primary": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "UGC 候補を強制 primary 化 (priority_score 順を無視)",
        "description": "Phase C-3: admin が UGC 候補を確認した結果、現 primary より正確と判断した時に手動で is_primary を切り替える。同 (lot, field) の他行を is_primary=false、対象行を true + approved_at 埋め。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "primary 切替結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "field_value not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationFieldValuesPromoteToPrimaryCreate"
      }
    },
    "/v1/admin/parking-moderation/payloads/unmatched": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "landing zone (parking_lot_id IS NULL の payload) 一覧",
        "description": "Phase A の取り込みで既存マスタにマッチしなかった raw payload。admin が既存 lot にリンクするか、新規 lot を作成するか判断する材料。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "sources",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "landing zone 一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationUnmatchedPayloadsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsUnmatchedList"
      }
    },
    "/v1/admin/parking-moderation/payloads/{id}/link": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "landing zone の payload を既存 lot にリンク",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationLinkBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "link 結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "unmatched payload not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsLinkCreate"
      }
    },
    "/v1/admin/parking-moderation/payloads/{id}/promote-new": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "landing zone payload を新規 lot として登録 (status='pending')",
        "description": "payload の lat/lng で parking_lots_master に status='pending' で 1 行作成、payload を link。作成後は「駐車場 (pending)」 タブで admin が承認 → active 化する。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationPayloadReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "新規 lot 作成成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "lot_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "lot_id",
                    "code"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "payload に lat/lng が無い等",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "payload not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsPromoteNewCreate"
      }
    },
    "/v1/admin/parking-moderation/payloads/{id}/reject": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "landing zone payload を却下 (soft delete)",
        "description": "payload を deleted_at=NOW() で soft delete。Landing zone から消える。raw データは残るので集計や監査には使える。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationPayloadReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "却下結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "payload not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsReject"
      }
    },
    "/v1/admin/parking-moderation/payloads/bulk-reject": {
      "patch": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "未マッチ取り込みデータを一括却下 (soft delete)",
        "description": "選択した payload をまとめて deleted_at=NOW() で soft delete。ノイズの大量処理用。raw データは残るので集計や監査には使える。",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationPayloadBulkRejectBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "一括却下結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsBulkRejectUpdate"
      }
    },
    "/v1/admin/parking-moderation/payloads/{id}/detail": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "landing zone payload の詳細 (lazy fetch)",
        "description": "payload jsonb 本体と、payload から抽出した lat/lng、100m 圏内の既存 lot 候補を返す。/unmatched は payload を返さないため、行展開時にこれを叩く想定。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "payload 詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationUnmatchedPayloadDetail"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "payload not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsDetailList"
      }
    },
    "/v1/admin/parking-moderation/payloads/{id}/nearby-lots": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "landing zone payload の近傍 lot 候補",
        "description": "Phase C-4: payload.payload JSON から lat/lng を抽出し、その近傍の parking_lots_master を返す。admin が地図 + 候補リスト UI で「同じ駐車場かどうか」判断する材料。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "radius_m",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "近傍 lot 候補一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationNearbyLotsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "payload not found or no coords",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationPayloadsNearbyLotsList"
      }
    },
    "/v1/admin/parking-moderation/denials": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "✗ tap が積み重なった field_value 一覧",
        "description": "Phase E-1: web/home の「これで合ってる？」 ✗ tap を field_value 単位で集計。denials_dismissed_at 以降の ✗ のみカウント。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "window_days",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "min_count",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "field_name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "✗ 集計一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationDenialsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationDenialsList"
      }
    },
    "/v1/admin/parking-moderation/denials/{id}/dismiss": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "✗ 報告を「無視 (ノイズ)」扱いにする",
        "description": "denials_dismissed_at = NOW() を更新。以降の ✗ は新規分のみカウントされる。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationDenialReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "無視結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "field_value not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationDenialsDismissCreate"
      }
    },
    "/v1/admin/parking-moderation/denials/{id}/soft-delete": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "field_value を soft delete",
        "description": "deleted_at = NOW(), is_primary=false を更新。trg_pfv_recompute_primary が別行を primary に昇格。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationDenialReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "soft delete 結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "field_value not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationDenialsSoftDelete"
      }
    },
    "/v1/admin/parking-moderation/denials/{id}/revert-lot": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "field_value を含む lot を pending に戻す",
        "description": "含 parking_lots_master を status='active' → 'pending' に。再度 admin 承認待ちに引き戻す。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationDenialReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "revert 結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer"
                    },
                    "lot_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reset_field_values": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "updated",
                    "lot_id",
                    "reset_field_values"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "field_value or active lot not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationDenialsRevertLotCreate"
      }
    },
    "/v1/admin/parking-moderation/feedback": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "フィールド フィードバック統合一覧 (UGC 候補 + ✗ 報告)",
        "description": "active な lot 配下の primary field_value で「✗ tap が 1 件以上」 or 「UGC 候補が存在」の 1 行 = 1 (lot, field) view。UGC 候補は同 (lot, field) で source='ugc' AND is_primary=false の confirms_n 最大行。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "window_days",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "ugc_min_confirms_n",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "field_name",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "フィードバック一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationFeedbackList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationFeedbackList"
      }
    },
    "/v1/admin/parking-moderation/ingest-schedules": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "ingest schedule 一覧 (複数 source × 複数 name)",
        "responses": {
          "200": {
            "description": "schedule 一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationIngestSchedulesList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestSchedulesList"
      }
    },
    "/v1/admin/parking-moderation/ingest-schedules/{source}/{name}": {
      "put": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "ingest schedule を upsert (新規作成または更新)",
        "description": "(source, name) が無ければ新規作成、有れば enabled / interval_days / bbox / max_api_calls を更新。enabled = true 化された瞬間 next_run_at = NOW() で即時 cron pickup させる。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "field",
                "ugc",
                "akippa",
                "navitime",
                "google",
                "osm",
                "kokudo",
                "jichitai",
                "import"
              ]
            },
            "required": true,
            "name": "source",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleUpsertBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "upsert 結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleItem"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestSchedulesReplace"
      },
      "delete": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "ingest schedule を削除",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "field",
                "ugc",
                "akippa",
                "navitime",
                "google",
                "osm",
                "kokudo",
                "jichitai",
                "import"
              ]
            },
            "required": true,
            "name": "source",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "削除結果",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "deleted"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestSchedulesDelete"
      }
    },
    "/v1/admin/parking-moderation/ingest-schedules/{source}/{name}/duplicate": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "同 source 別 name にコピー (新規 schedule は enabled=false)",
        "description": "bbox / interval_days / max_api_calls を引き継ぎ、新 name で行を作成する。新規行はenabled=false で作られるため、明示的に有効化するまで cron 対象にならない。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "field",
                "ugc",
                "akippa",
                "navitime",
                "google",
                "osm",
                "kokudo",
                "jichitai",
                "import"
              ]
            },
            "required": true,
            "name": "source",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleDuplicateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "複製結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleItem"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "dst name が既に存在",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestSchedulesDuplicate"
      }
    },
    "/v1/admin/parking-moderation/ingest-schedules/{source}/{name}/run-now": {
      "post": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "schedule を即時実行 (cron 待たず adapter を起動)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "field",
                "ugc",
                "akippa",
                "navitime",
                "google",
                "osm",
                "kokudo",
                "jichitai",
                "import"
              ]
            },
            "required": true,
            "name": "source",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            },
            "required": true,
            "name": "name",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleRunNowBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "run-now 結果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationIngestScheduleRunNowResult"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "schedule or adapter not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestSchedulesRunNowCreate"
      }
    },
    "/v1/admin/parking-moderation/ingest-runs": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "取り込み実行履歴 一覧",
        "description": "Phase E: 各 ingest 実行の取得件数 + 現状ステータス集計を返す。「取込時 auto マッチ」 と「現在の紐付け数」 の差分から admin の動作量が分かる。",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "sources",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "window_days",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "実行履歴一覧",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationIngestRunsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestRunsList"
      }
    },
    "/v1/admin/parking-moderation/ingest-runs/{run_id}/payloads": {
      "get": {
        "tags": [
          "admin",
          "parking-moderation"
        ],
        "summary": "1 run の payload 詳細",
        "description": "Phase E: 指定 run で取り込まれた payload 一覧。現状ステータス (linked / pending / rejected) 付き。states クエリで絞り込み可能。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "run_id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "states",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "payload 詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminParkingModerationIngestRunPayloadsList"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminParkingModerationIngestRunsPayloadsList"
      }
    },
    "/v1/admin/place-discounts": {
      "get": {
        "tags": [
          "施設優待 / Place Discounts"
        ],
        "summary": "施設優待一覧 / Place Discount List",
        "description": "駐車場 / 施設で絞り込み可能。place_name / lot_name を JOIN で同梱。\n\n### ⚠️ DEPRECATED\n**このエンドポイントは 2027-01-01 に廃止予定**。`GET /cursor` に移行してください。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "place_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active_only",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminPlaceDiscount"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "items",
                    "total",
                    "page",
                    "limit"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlaceDiscountsList"
      },
      "post": {
        "tags": [
          "施設優待 / Place Discounts"
        ],
        "summary": "施設優待を作成 / Create Place Discount",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "place_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "lot_id": {
                    "type": "string",
                    "format": "uuid",
                    "examples": [
                      "00000000-0000-0000-0000-000000000000"
                    ]
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "terms": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "place_id",
                  "lot_id",
                  "title"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminPlaceDiscount"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlaceDiscountsCreate"
      }
    },
    "/v1/admin/place-discounts/cursor": {
      "get": {
        "tags": [
          "施設優待 / Place Discounts"
        ],
        "summary": "施設優待一覧（Cursor）/ Place Discount List (Cursor)",
        "description": "### 用途\n`place_discounts` を cursor ベースで取得する。`d.created_at DESC` keyset。\noffset 版 `GET /` は 2027-01-01 廃止予定。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "lot_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "place_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active_only",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_deleted",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminPlaceDiscount"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlaceDiscountsCursorList"
      }
    },
    "/v1/admin/place-discounts/{place_id}/{lot_id}": {
      "patch": {
        "tags": [
          "施設優待 / Place Discounts"
        ],
        "summary": "施設優待を更新 / Update Place Discount",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "place_id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lot_id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "terms": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminPlaceDiscount"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlaceDiscountsUpdate"
      },
      "delete": {
        "tags": [
          "施設優待 / Place Discounts"
        ],
        "summary": "施設優待をソフト削除 / Soft Delete Place Discount",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "place_id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "lot_id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除済み"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminPlaceDiscountsDelete"
      }
    },
    "/v1/admin/app-config": {
      "get": {
        "tags": [
          "アプリ設定 / App Config"
        ],
        "summary": "アプリ設定を取得 / Get App Config",
        "description": "### 用途\n管理者ポータルの「アプリ設定」画面ロード時に呼ぶ。\n\n### 認証・認可\n`requireAdmin`。\n\n### 挙動\n- app_config (KV) テーブルから関連キーを一括取得\n- DB に無いキーは TS 側 default (api/src/config/schema.ts) にフォールバック",
        "responses": {
          "200": {
            "description": "現在のアプリ設定",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAppConfig"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "未認証",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "管理者ではない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAppConfigList"
      },
      "put": {
        "tags": [
          "アプリ設定 / App Config"
        ],
        "summary": "アプリ設定を更新 / Update App Config",
        "description": "### 用途\n強制アップデート下限バージョン・メンテナンスモード・ストア URL・徒歩コスト既定値を変更する。\n\n### 挙動・制約\n- 指定されたフィールドだけ更新 (undefined のフィールドはそのまま)\n- 各フィールドは個別に app_config (KV) へ UPSERT される\n- history (app_config_history) にトリガーで自動記録\n- `maintenance_message` / `store_url_*` は null で明示クリア可",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateAppConfig"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後のアプリ設定",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminAppConfig"
                }
              }
            }
          },
          "400": {
            "description": "不正なリクエスト",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "未認証",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "管理者ではない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAppConfigReplace"
      }
    },
    "/v1/admin/app-config/history": {
      "get": {
        "tags": [
          "アプリ設定 / App Config"
        ],
        "summary": "設定変更履歴 / Config Change History",
        "description": "### 用途\n管理者ポータルの設定変更履歴ページで誰が何をいつ変えたか確認する。\n\n### クエリパラメータ\n- `key`: 絞り込みたいキー名（省略で全キー）\n- `limit`: 取得件数（省略で 50、最大 200）",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "絞り込みキー名"
            },
            "required": false,
            "name": "key",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "取得件数（1〜200、省略=50）"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "設定変更履歴",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AppConfigHistoryItem"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "未認証",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "管理者ではない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAppConfigHistoryList"
      }
    },
    "/v1/admin/app-config/all-keys": {
      "get": {
        "tags": [
          "アプリ設定 / App Config"
        ],
        "summary": "全設定キー一覧 / All Config Keys",
        "description": "### 用途\n管理者ポータルの汎用設定エディタ画面ロード時に呼ぶ。\n\n### 挙動\n- 全 ConfigKey の現在値（DB 優先 / 無ければ TS default）\n- 各キーの description・domain・default・type_hint・min/max/enum_values・sensitive を付与\n- updated_at は app_config テーブルから JOIN（未登録は null）",
        "responses": {
          "200": {
            "description": "全設定キーとメタデータ",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AppConfigItem"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "未認証",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "管理者ではない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAppConfigAllKeysList"
      }
    },
    "/v1/admin/app-config/{key}": {
      "patch": {
        "tags": [
          "アプリ設定 / App Config"
        ],
        "summary": "単一設定キーを更新 / Update Single Config Key",
        "description": "### 用途\n汎用設定エディタから個別キーを更新する。\n\n### 挙動\n- body.value を対象キーの zod schema で validate してから UPSERT\n- 更新後の値を同形式で返す\n- history (app_config_history) にトリガーで自動記録",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "key",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後のキー情報",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppConfigItem"
                }
              }
            }
          },
          "400": {
            "description": "不正な値",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "未認証",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "管理者ではない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "キーが存在しない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminAppConfigUpdate"
      }
    },
    "/v1/admin/client-events": {
      "get": {
        "tags": [
          "クライアントイベント / Client Events"
        ],
        "summary": "クライアントイベント一覧 / Client Event List",
        "description": "### 用途\nモバイルアプリ / Web クライアントが送ってきたイベント（crash / error / performance / ux / lifecycle）を\n管理者ポータルから閲覧する。QA・障害調査・パフォーマンス監視で使う。\n\n### ⚠️ DEPRECATED\n**このエンドポイントは 2027-01-01 に廃止予定**。`GET /cursor` に移行してください。\n`Deprecation: true` / `Sunset: ...` / `Link: <...>; rel=\"successor-version\"` ヘッダを返す。\nanalytics.client_events は大テーブルなので offset+COUNT(*) は O(n) で遅くなる。\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用。\n\n### 挙動・制約\n- `created_at DESC` で並ぶ\n- `event_type` / `severity` / `device_platform` / `user_id` で絞り込み可\n- 総件数は別 COUNT クエリで取得\n- stack_trace / metadata は詳細エンドポイント側で返す",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 はじまりのページ番号",
              "examples": [
                "1"
              ]
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 2000）",
              "examples": [
                "20"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "cursor ページング利用時の不透明トークン。offset ページング（page/limit）とは排他。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/ClientEventType"
            },
            "required": false,
            "name": "event_type",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/ClientEventSeverity"
            },
            "required": false,
            "name": "severity",
            "in": "query"
          },
          {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ClientEventDevicePlatform"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "device_platform",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（deprecated / offset）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminClientEventListItem"
                      }
                    },
                    "page": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "total_is_estimate": {
                      "type": "boolean",
                      "description": "true のとき total は pg_class.reltuples 由来の概算値（数千行ズレ得る）。exact COUNT のときは field 自体が省略される。"
                    }
                  },
                  "required": [
                    "items",
                    "page",
                    "limit",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminClientEventsList"
      }
    },
    "/v1/admin/client-events/cursor": {
      "get": {
        "tags": [
          "クライアントイベント / Client Events"
        ],
        "summary": "クライアントイベント一覧（Cursor）/ Client Event List (Cursor)",
        "description": "### 用途\n`analytics.client_events` を cursor ベースで取得する。深いページでも O(log n)。\n\n### cursor の使い方\n1. 初回は `cursor` パラメータを省略。\n2. レスポンスの `next_cursor` が `null` でなければ、次のリクエストに `cursor=<value>` を渡す。\n3. `has_more=false` になったら終端。\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用。\n\n### フィルタ\n- `event_type` / `severity` / `device_platform` / `user_id`\n- stack_trace / metadata は `GET /{id}` で取得（list では省略）。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "前回レスポンスの next_cursor をそのまま渡すと次ページを返す。空なら先頭から。"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$",
              "description": "1 ページあたりの件数（最大 500）",
              "examples": [
                "50"
              ]
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/ClientEventType"
            },
            "required": false,
            "name": "event_type",
            "in": "query"
          },
          {
            "schema": {
              "$ref": "#/components/schemas/ClientEventSeverity"
            },
            "required": false,
            "name": "severity",
            "in": "query"
          },
          {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ClientEventDevicePlatform"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "device_platform",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "一覧（cursor）",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminClientEventListItem"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminClientEventsCursorList"
      }
    },
    "/v1/admin/client-events/{id}": {
      "get": {
        "tags": [
          "クライアントイベント / Client Events"
        ],
        "summary": "クライアントイベント詳細 / Client Event Detail",
        "description": "### 用途\n単一イベントの全フィールド（stack_trace / metadata 含む）を返す。\n障害の詳細調査・スタックトレース確認で使用する。\n\n### 認証・認可\n`requireAdmin` を経由する管理者専用。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "詳細",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminClientEventDetail"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "見つからない",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminClientEventsGet"
      }
    },
    "/v1/admin/i18n-messages": {
      "get": {
        "tags": [
          "i18n"
        ],
        "summary": "i18n message 一覧",
        "description": "### 用途\nモバイル辞書のエントリを管理用に一覧する。`lang` / `namespace` / `key_prefix`\nでフィルタ可能。最大 5000 件 (key 昇順)。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "examples": [
                "ja"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "examples": [
                "common"
              ]
            },
            "required": false,
            "name": "namespace",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "examples": [
                "empty."
              ]
            },
            "required": false,
            "name": "key_prefix",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "i18n message 一覧",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminI18nMessage"
                  }
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminI18nMessagesList"
      },
      "post": {
        "tags": [
          "i18n"
        ],
        "summary": "i18n message を新規作成",
        "description": "### 用途\n辞書に新エントリを追加する。成功すると mobile bundle の version (md5 hash) が変わるので、\n次回 client 起動時に自動で辞書が更新される。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。\n\n### 挙動\n- `(lang, key)` 重複は DB UNIQUE 制約で 409\n- `key` は `[a-z0-9_.]+` 128 字以内\n- `value` は 4096 字以内",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lang": {
                    "type": "string",
                    "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})?$",
                    "examples": [
                      "ja"
                    ]
                  },
                  "key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "pattern": "^[a-z0-9_.]+$",
                    "examples": [
                      "empty.search.title"
                    ]
                  },
                  "value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096,
                    "examples": [
                      "検索結果なし"
                    ]
                  },
                  "namespace": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "examples": [
                      "common"
                    ]
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "lang",
                  "key",
                  "value"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "作成済み",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminI18nMessage"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminI18nMessagesCreate"
      }
    },
    "/v1/admin/i18n-messages/{id}": {
      "patch": {
        "tags": [
          "i18n"
        ],
        "summary": "i18n message を更新",
        "description": "### 用途\n既存辞書エントリの `value` / `namespace` / `description` を部分更新する。\n`lang` / `key` は不変 (SSoT の同一性保持)。変更したい場合は DELETE → POST。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096
                  },
                  "namespace": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新後",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminI18nMessage"
                }
              }
            }
          },
          "400": {
            "description": "validation_error / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminI18nMessagesUpdate"
      },
      "delete": {
        "tags": [
          "i18n"
        ],
        "summary": "i18n message を削除 (hard delete)",
        "description": "### 用途\n辞書エントリを物理削除する。削除すると bundle version が変わり、client の次回起動で\n反映される。ただし client 側で古い key を参照しているコードが残っていると表示が\nフォールバック文言になる点に注意。\n\n### 認証・認可\n要 Bearer JWT + `requireAdmin`。",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "examples": [
                "00000000-0000-0000-0000-000000000000"
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "削除成功"
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "adminI18nMessagesDelete"
      }
    }
  },
  "webhooks": {}
}
