openapi: 3.1.2
info:
  title: NDB OpenData Hub Public API
  version: 1.1.0
  description: |
    第10回・第11回NDBオープンデータ（特定健診）を対象とした読み取り専用API。
    - `/api/v1/*` は REST / OpenAPI クライアントと ChatGPT Actions 向けの正本です。
    - Remote MCPクライアント（ChatGPT Apps、Claude、Codex）は別の `POST /mcp` を使用します。
    - `/api/mcp/*` は旧式RESTエンドポイントです。移行確認が終わるまで後方互換目的で残します。
    - 原値表示APIでは `release=10|11|latest` を使い、未指定時のlatestは第11回です。
    - `data_year` は既存DBの内部dataset/storage互換キー（第10回=2023、第11回=2024）であり、健診対象年度ではありません。健診対象年度は `health_checkup_year`（第10回=2022、第11回=2023）を参照してください。
    - `inspection-stats` や比較系は派生分析値を含むため、Excel原票セル値の直接表示とは分けて扱います。
servers:
  - url: https://ndbopendata-hub.com
paths:
  /api/v1/capabilities:
    get:
      summary: APIの能力一覧を取得
      operationId: getCapabilities
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilitiesResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/v1/items:
    get:
      summary: 検査項目辞書を取得
      operationId: listInspectionItemsV1
      parameters:
        - in: query
          name: dataset
          schema:
            type: string
            enum: [inspection]
          required: false
          description: 対象データセット。現状は `inspection` のみ。
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemsResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/v1/areas:
    get:
      summary: 地域辞書（都道府県／二次医療圏）を取得
      operationId: listAreasV1
      parameters:
        - in: query
          name: type
          schema:
            type: string
            enum: [prefecture, secondary_medical_area]
          required: false
          description: 取得対象。省略時は `prefecture`。
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AreasResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/v1/range-labels:
    get:
      summary: 検査項目の範囲ラベル一覧（安定ID付き）
      operationId: getRangeLabelsV1
      parameters:
        - in: query
          name: item_name
          schema: { type: string, minLength: 1, maxLength: 100 }
          required: true
        - in: query
          name: record_mode
          schema: { type: string, enum: [basic, detailed, all] }
          required: false
          description: 基本健診（basic）／詳細健診（detailed）。省略時は `all`。
        - in: query
          name: gender
          schema: { type: string, enum: [M, F, all] }
          required: false
          description: 性別で絞り込む場合に指定。
        - in: query
          name: release
          schema: { type: string, enum: ['10', '11', latest] }
          required: false
          description: 公開回。未指定または `latest` は第11回。
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RangeLabelsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: 指定項目のラベルが存在しない
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/v1/inspection-stats:
    get:
      summary: 検査データの集計を取得
      operationId: getInspectionStatsV1
      parameters:
        - in: query
          name: item_name
          schema: { type: string, minLength: 1, maxLength: 100 }
          required: true
        - in: query
          name: record_mode
          schema: { type: string, enum: [basic, detailed, all] }
          required: false
          description: 基本／詳細を選択。省略時は `all`。
        - in: query
          name: area_type
          schema: { type: string, enum: [prefecture, secondary_medical_area] }
        - in: query
          name: prefecture_code
          schema: { type: string, pattern: '^[0-9]{2}$' }
        - in: query
          name: prefecture_name
          schema: { type: string, maxLength: 50 }
        - in: query
          name: area_code
          schema: { type: string, pattern: '^[0-9]{4}$' }
        - in: query
          name: area_name
          schema: { type: string, maxLength: 100 }
        - in: query
          name: gender
          schema: { type: string, enum: [M, F, all] }
        - in: query
          name: age_group
          schema: { type: string, pattern: '^[0-9]{2}-[0-9]{2}$' }
        - in: query
          name: value_range
          schema:
            oneOf:
              - type: string
              - type: array
                items:
                  type: string
          style: form
          explode: true
          description: |
            範囲フィルタ。`range_id`（推奨）または `label` を指定。
            例: `value_range=142` / `value_range=142&value_range=143` / `value_range=142,143` / `value_range=["142","143"]`
        - in: query
          name: page
          schema: { type: integer, minimum: 1, maximum: 100 }
        - in: query
          name: per_page
          schema: { type: integer, minimum: 1, maximum: 200 }
        - in: query
          name: format
          schema: { type: string, enum: [json, summary, markdown_table] }
        - in: query
          name: release
          schema: { type: string, enum: ['10', '11', latest] }
          required: false
          description: 公開回。未指定または `latest` は第11回。
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InspectionStatsResponseV1'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/v1/health:
    get:
      summary: API稼働状態
      operationId: getHealth
      responses:
        '200': { description: OK }
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/v1/version:
    get:
      summary: データセット・スキーマのバージョン情報
      operationId: getVersionInfo
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_version: { type: string }
                  schema_version: { type: string }
                  dataset_year:
                    type: string
                    description: 既存DBの内部dataset/storage互換キー。健診対象年度ではない。
                  dataset_year_semantics:
                    type: string
                    enum: [internal_dataset_storage_key]
                  latest_health_checkup_year:
                    type: integer
                    enum: [2023]
                    description: latest公開回（第11回）の健診対象年度。
                  default_release: { type: string, enum: [latest] }
                  latest_release: { type: string, enum: ['11'] }
                  available_releases:
                    type: array
                    items:
                      type: object
                      properties:
                        release: { type: string }
                        release_number: { type: string }
                        data_year:
                          type: integer
                          enum: [2023, 2024]
                          description: 既存DBの内部dataset/storage互換キー。健診対象年度ではない。
                        data_year_semantics:
                          type: string
                          enum: [internal_dataset_storage_key]
                        health_checkup_year:
                          type: integer
                          enum: [2022, 2023]
                          description: 公開回が表す健診対象年度。
                        label: { type: string }
                  last_refreshed_at: { type: string, nullable: true }
                  git_commit: { type: string, nullable: true }
                  build_time: { type: string, nullable: true }
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /api/mcp/inspection-stats:
    get:
      deprecated: true
      summary: 旧式の検査データ集計（後方互換用）
      operationId: getInspectionStatsLegacy
      responses:
        '200':
          description: OK
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /api/mcp/inspection-range-labels:
    get:
      deprecated: true
      summary: 旧式の範囲ラベル一覧（後方互換用）
      operationId: getRangeLabelsLegacy
      responses:
        '200':
          description: OK
        '429':
          $ref: '#/components/responses/TooManyRequests'

components:
  responses:
    BadRequest:
      description: 無効なパラメータ
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: レート制限超過
      headers:
        X-RateLimit-Limit:
          schema: { type: string }
        X-RateLimit-Remaining:
          schema: { type: string }
        Retry-After:
          schema: { type: string }
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ReleaseMetadata:
      type: object
      required:
        - public_number
        - release_number
        - data_year
        - data_year_semantics
        - health_checkup_year
        - label
      properties:
        public_number:
          type: string
          enum: ['10', '11']
        release_number:
          type: string
          enum: [第10回, 第11回]
        data_year:
          type: integer
          enum: [2023, 2024]
          description: 既存DBの内部dataset/storage互換キー。健診対象年度ではない。
        data_year_semantics:
          type: string
          enum: [internal_dataset_storage_key]
        health_checkup_year:
          type: integer
          enum: [2022, 2023]
          description: 公開回が表す健診対象年度。
        label:
          type: string
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
            hint: { type: string, nullable: true }
    CapabilitiesResponse:
      type: object
      properties:
        datasets:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              record_modes:
                type: array
                items: { type: string }
              dimensions:
                type: object
              formats:
                type: array
                items: { type: string }
              primary_endpoint: { type: string }
        rate_limit:
          type: object
          properties:
            limit_per_window: { type: integer }
            window_ms: { type: integer }
        discovery_endpoints:
          type: object
          additionalProperties: { type: string }
        formats:
          type: array
          items: { type: string }
        version: { type: string }
    ItemsResponse:
      type: object
      properties:
        dataset: { type: string }
        total: { type: integer }
        items:
          type: array
          items:
            type: object
            properties:
              item_id: { type: integer }
              item_name: { type: string }
              item_category: { type: string, nullable: true }
              item_unit: { type: string, nullable: true }
    AreasResponse:
      type: object
      properties:
        type: { type: string }
        total: { type: integer }
        areas:
          type: array
          items:
            type: object
            properties:
              prefecture_code: { type: string, nullable: true }
              prefecture_name: { type: string, nullable: true }
              region_name: { type: string, nullable: true }
              area_code: { type: string, nullable: true }
              area_name: { type: string, nullable: true }
    RangeLabelsResponse:
      type: object
      properties:
        item_name: { type: string }
        record_mode: { type: string }
        gender: { type: string }
        release:
          $ref: '#/components/schemas/ReleaseMetadata'
        ranges:
          type: array
          items:
            type: object
            properties:
              range_id: { type: string }
              label: { type: string }
              sort_order: { type: integer }
              gender: { type: string }
              dataset_types:
                type: array
                items:
                  type: object
                  properties:
                    type: { type: string }
                    total_count: { type: integer }
        meta:
          type: object
          properties:
            hint: { type: string }
    InspectionStatRow:
      type: object
      properties:
        range_id: { type: string }
        label: { type: string }
        dataset_type: { type: string }
        gender: { type: string }
        age_group: { type: string }
        area_type: { type: string }
        area_code: { type: string, nullable: true }
        area_name: { type: string, nullable: true }
        prefecture_code: { type: string, nullable: true }
        prefecture_name: { type: string, nullable: true }
        count: { type: integer }
        population: { type: integer, nullable: true }
        rate: { type: number, nullable: true, format: float }
        item:
          type: object
          properties:
            id: { type: integer }
            name: { type: string }
            category: { type: string, nullable: true }
            unit: { type: string, nullable: true }
    InspectionStatsResponseV1:
      type: object
      properties:
        query:
          type: object
        meta:
          type: object
          properties:
            unit: { type: string }
            denominator: { type: string }
            rate_limit:
              type: object
              properties:
                limit: { type: integer }
                remaining: { type: integer }
                window_ms: { type: integer }
            notes:
              type: array
              items: { type: string }
            available_ranges:
              $ref: '#/components/schemas/RangeLabelsResponse/properties/ranges'
            totals:
              type: object
              properties:
                total_inspections: { type: integer }
                total_rows: { type: integer }
            release:
              $ref: '#/components/schemas/ReleaseMetadata'
        data:
          oneOf:
            - type: string
            - type: array
              items:
                $ref: '#/components/schemas/InspectionStatRow'
