openapi: 3.1.0

info:
  title: imo-vmdb REST API
  version: "1"
  description: >
    Query rate and magnitude observations from the
    [Visual Meteor Database (VMDB)](https://www.imo.net/members/imo_vmdb/)
    stored in an *imo-vmdb* database.

    Full documentation: https://imo-vmdb.readthedocs.io/

servers:
  - url: /api/v1
    description: imo-vmdb web server (default port 8000)

paths:

  /rates:
    get:
      summary: Rate observations
      description: >
        Returns normalised rate observations.
        All filter parameters are optional and combined with AND.
        Multiple values for `shower`, `session_id`, and `rate_id` are
        combined with OR.

        Always returns the unpaginated total count in the `X-Total-Count`
        response header.  Use `limit=0` to obtain just the count.
      parameters:
        - $ref: '#/components/parameters/shower'
        - $ref: '#/components/parameters/period_start'
        - $ref: '#/components/parameters/period_end'
        - $ref: '#/components/parameters/sl_min'
        - $ref: '#/components/parameters/sl_max'
        - $ref: '#/components/parameters/lim_magn_min'
        - $ref: '#/components/parameters/lim_magn_max'
        - name: sun_alt_max
          in: query
          description: Maximum solar altitude in degrees.
          schema:
            type: number
        - name: moon_alt_max
          in: query
          description: Maximum lunar altitude in degrees.
          schema:
            type: number
        - $ref: '#/components/parameters/session_id'
        - name: rate_id
          in: query
          description: Filter by rate observation IDs (repeatable).
          schema:
            type: array
            items:
              type: integer
          style: form
          explode: true
        - $ref: '#/components/parameters/rates_include'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: order_by
          in: query
          description: Sort column.
          schema:
            type: string
            enum: [id, period_start, period_end, sl_start, lim_magn]
        - $ref: '#/components/parameters/order'
        - $ref: '#/components/parameters/fields'
      responses:
        "200":
          description: Successful response.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/XTotalCount'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RatesResponse'
        "400":
          $ref: '#/components/responses/BadRequest'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /rates/{id}:
    get:
      summary: Single rate observation
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateObservation'
        "404":
          $ref: '#/components/responses/NotFound'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /magnitudes:
    get:
      summary: Magnitude observations
      description: >
        Returns normalised magnitude observations.
        All filter parameters are optional and combined with AND.
        Multiple values for `shower`, `session_id`, and `magn_id` are
        combined with OR.

        Always returns the unpaginated total count in the `X-Total-Count`
        response header.  Use `limit=0` to obtain just the count.
      parameters:
        - $ref: '#/components/parameters/shower'
        - $ref: '#/components/parameters/period_start'
        - $ref: '#/components/parameters/period_end'
        - $ref: '#/components/parameters/sl_min'
        - $ref: '#/components/parameters/sl_max'
        - $ref: '#/components/parameters/lim_magn_min'
        - $ref: '#/components/parameters/lim_magn_max'
        - $ref: '#/components/parameters/session_id'
        - name: magn_id
          in: query
          description: Filter by magnitude observation IDs (repeatable).
          schema:
            type: array
            items:
              type: integer
          style: form
          explode: true
        - $ref: '#/components/parameters/magnitudes_include'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: order_by
          in: query
          description: Sort column.
          schema:
            type: string
            enum: [id, period_start, period_end, sl_start, lim_magn]
        - $ref: '#/components/parameters/order'
        - $ref: '#/components/parameters/fields'
      responses:
        "200":
          description: Successful response.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/XTotalCount'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MagnitudesResponse'
        "400":
          $ref: '#/components/responses/BadRequest'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /magnitudes/{id}:
    get:
      summary: Single magnitude observation
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MagnitudeObservation'
        "404":
          $ref: '#/components/responses/NotFound'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /sessions:
    get:
      summary: Observation sessions
      description: >
        Returns observation sessions, optionally restricted to particular
        observers or to sessions that have at least one rate or magnitude
        observation matching the given filters.


        All observation-level filters (`shower`, `period_*`, `sl_*`,
        `lim_magn_*`) are evaluated via an EXISTS-join on `rate` and
        `magnitude`: a session is returned when at least one of its
        observations satisfies the criteria.  When `include=rates` or
        `include=magnitudes` is requested, the included observations
        are restricted by the same filters and to the session IDs in
        the response page.
      parameters:
        - name: observer_id
          in: query
          description: Filter by observer IDs (repeatable).
          schema:
            type: array
            items:
              type: integer
          style: form
          explode: true
        - $ref: '#/components/parameters/shower'
        - $ref: '#/components/parameters/period_start'
        - $ref: '#/components/parameters/period_end'
        - $ref: '#/components/parameters/sl_min'
        - $ref: '#/components/parameters/sl_max'
        - $ref: '#/components/parameters/lim_magn_min'
        - $ref: '#/components/parameters/lim_magn_max'
        - $ref: '#/components/parameters/sessions_include'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: order_by
          in: query
          description: Sort column.
          schema:
            type: string
            enum: [id, country, observer_id]
        - $ref: '#/components/parameters/order'
      responses:
        "200":
          description: Successful response.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/XTotalCount'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionsResponse'
        "400":
          $ref: '#/components/responses/BadRequest'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /sessions/{id}:
    get:
      summary: Single observation session
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        "404":
          $ref: '#/components/responses/NotFound'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /showers:
    get:
      summary: Shower reference data
      description: Returns all meteor showers from the reference data.
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Shower'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /showers/active:
    get:
      summary: Showers active on a given date
      description: >
        Returns all showers whose activity period covers the given calendar
        date (year is ignored, year-wrapping showers handled correctly).
      parameters:
        - name: date
          in: query
          description: ISO date `YYYY-MM-DD`.  Defaults to today (UTC).
          schema:
            type: string
            format: date
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Shower'
        "400":
          $ref: '#/components/responses/BadRequest'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /showers/{iau_code}:
    get:
      summary: Single meteor shower
      parameters:
        - name: iau_code
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Shower'
        "404":
          $ref: '#/components/responses/NotFound'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /showers/{iau_code}/radiants:
    get:
      summary: Radiant positions for a shower
      description: >
        Returns all radiant entries for the given shower, ordered by
        `(month, day)`.  Empty array if the shower has no radiants.
      parameters:
        - name: iau_code
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Radiant'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /stats/meta:
    get:
      summary: Database scope summary
      description: >
        Returns the total number of sessions, rates and magnitudes plus the
        covered date range.  Useful for clients to discover what's in the
        database before issuing more specific queries.
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatsMeta'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /stats/by-shower:
    get:
      summary: Per-shower aggregate counts
      parameters:
        - $ref: '#/components/parameters/period_start'
        - $ref: '#/components/parameters/period_end'
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ShowerStat'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /stats/by-country:
    get:
      summary: Per-country aggregate counts
      parameters:
        - $ref: '#/components/parameters/period_start'
        - $ref: '#/components/parameters/period_end'
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CountryStat'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /stats/by-year:
    get:
      summary: Per-year aggregate counts
      parameters:
        - $ref: '#/components/parameters/period_start'
        - $ref: '#/components/parameters/period_end'
      responses:
        "200":
          description: Successful response.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/YearStat'
        "500":
          $ref: '#/components/responses/ServerError'
        "503":
          $ref: '#/components/responses/NoDB'

  /health:
    get:
      summary: Liveness/readiness probe
      description: >
        Returns 200 with `{"status": "ok", "database": "ok"}` when the
        database is reachable; 503 with `{"status": "degraded", "database": <reason>}`
        otherwise.  Suitable for load balancers and Kubernetes probes.
      responses:
        "200":
          description: Database reachable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
        "503":
          description: Database unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'

  /openapi.yaml:
    get:
      summary: OpenAPI specification (YAML)
      description: Returns this OpenAPI 3.1 specification as YAML.
      responses:
        "200":
          description: OpenAPI YAML document.
          content:
            application/yaml:
              schema:
                type: string

  /openapi.json:
    get:
      summary: OpenAPI specification (JSON)
      description: Returns this OpenAPI 3.1 specification as JSON.
      responses:
        "200":
          description: OpenAPI JSON document.
          content:
            application/json:
              schema:
                type: object

components:

  parameters:

    shower:
      name: shower
      in: query
      description: >
        IAU shower code(s) (repeatable).
        Use `SPO` to include sporadic meteors (returned as `null` in the
        `shower` field).
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
      examples:
        single:
          value: [PER]
        multiple:
          value: [PER, GEM]
        with_sporadic:
          value: [PER, SPO]

    period_start:
      name: period_start
      in: query
      description: >
        Lower bound for `period_start`.  Strict ISO 8601:
        `YYYY-MM-DDTHH:MM:SS` (e.g. `2015-08-12T20:00:00`).  IMO data
        is UTC by convention; no timezone marker is accepted.
        Space-separator, date-only, trailing `Z` and `±HH:MM` offsets
        are rejected with HTTP 400.
      schema:
        type: string
        format: date-time
        example: "2015-08-12T20:00:00"

    period_end:
      name: period_end
      in: query
      description: >
        Upper bound for `period_end`.  Strict ISO 8601:
        `YYYY-MM-DDTHH:MM:SS`.  Same rules as `period_start` (no
        timezone marker, no space separator, no date-only).
      schema:
        type: string
        format: date-time
        example: "2015-08-31T23:59:59"

    sl_min:
      name: sl_min
      in: query
      description: Minimum solar longitude (degrees, `sl_start >=`).
      schema:
        type: number
        minimum: 0
        maximum: 360

    sl_max:
      name: sl_max
      in: query
      description: Maximum solar longitude (degrees, `sl_end <=`).
      schema:
        type: number
        minimum: 0
        maximum: 360

    lim_magn_min:
      name: lim_magn_min
      in: query
      description: Minimum limiting magnitude.
      schema:
        type: number

    lim_magn_max:
      name: lim_magn_max
      in: query
      description: Maximum limiting magnitude.
      schema:
        type: number

    session_id:
      name: session_id
      in: query
      description: Filter by session IDs (repeatable).
      schema:
        type: array
        items:
          type: integer
      style: form
      explode: true

    rates_include:
      name: include
      in: query
      description: >
        Comma-separated list of related data to include in the response.
        Supported values:

        * `sessions` — adds a `sessions` array of `Session` rows referenced
          by the returned rate observations.

        * `magnitudes` — adds a `magnitudes` array of full
          `MagnitudeObservation` rows: the magnitude observation
          referenced by each rate's `magn_id`.

        * `magnitude_details` — adds a `magnitude_details` array of
          `MagnitudeDetail` rows (per-magnitude-class frequencies) for
          the magnitude observations referenced by the returned rates.
          Can be requested independently of `magnitudes`; each detail
          row links back via `MagnitudeDetail.id == Rate.magn_id`.

        Unknown values yield HTTP 400.
      schema:
        type: string
        example: sessions,magnitudes,magnitude_details

    magnitudes_include:
      name: include
      in: query
      description: >
        Comma-separated list of related data to include in the response.
        Supported values:

        * `sessions` — adds a `sessions` array of `Session` rows referenced
          by the returned magnitude observations.

        * `magnitude_details` — adds a `magnitude_details` array of
          `MagnitudeDetail` rows (per-magnitude-class frequencies) for
          the returned magnitude observations.

        Unknown values yield HTTP 400.
      schema:
        type: string
        example: sessions,magnitude_details

    sessions_include:
      name: include
      in: query
      description: >
        Comma-separated list of related data to include in the response.
        Supported values:

        * `rates` — adds a `rates` array of full `RateObservation` rows
          for the sessions in the response page.

        * `magnitudes` — adds a `magnitudes` array of full
          `MagnitudeObservation` rows for the sessions in the response
          page.

        For per-magnitude-class frequencies (`MagnitudeDetail`), use the
        dedicated `/magnitudes` endpoint with `?session_id=…&include=magnitude_details`.

        Unknown values yield HTTP 400.
      schema:
        type: string
        example: rates,magnitudes

    limit:
      name: limit
      in: query
      description: >
        Maximum number of rows to return.  Use `limit=0` to obtain only the
        total count via the `X-Total-Count` header.  Without `limit`, all
        matching rows are returned (backwards-compatible behaviour).
      schema:
        type: integer
        minimum: 0

    offset:
      name: offset
      in: query
      description: Number of leading rows to skip.
      schema:
        type: integer
        minimum: 0

    order:
      name: order
      in: query
      description: Sort direction.
      schema:
        type: string
        enum: [asc, desc]
        default: asc

    fields:
      name: fields
      in: query
      description: >
        Comma-separated list of field names to include in the response.
        Unknown field names result in HTTP 400.
      schema:
        type: string
        example: id,shower,period_start,freq

  schemas:

    RateObservation:
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier of the rate observation.
        shower:
          type: ["string", "null"]
          description: IAU shower code, or null for sporadic meteors.
        period_start:
          type: string
          format: date-time
          description: >
            Start of the observation period in strict ISO 8601 form
            `YYYY-MM-DDTHH:MM:SS` (UTC, no timezone marker).
          example: "2015-08-12T22:00:00"
        period_end:
          type: string
          format: date-time
          description: >
            End of the observation period in strict ISO 8601 form
            `YYYY-MM-DDTHH:MM:SS` (UTC, no timezone marker).
          example: "2015-08-12T23:00:00"
        sl_start:
          type: number
          description: Solar longitude at period start (degrees).
        sl_end:
          type: number
          description: Solar longitude at period end (degrees).
        session_id:
          type: integer
          description: Reference to the observation session.
        freq:
          type: integer
          description: Number of observed meteors.
        lim_magn:
          type: number
          description: Limiting magnitude.
        t_eff:
          type: number
          description: Net observation time in hours.
        f:
          type: number
          description: Cloud cover correction factor.
        sidereal_time:
          type: number
          description: Sidereal time at midpoint.
        sun_alt:
          type: number
          description: Solar altitude in degrees.
        sun_az:
          type: number
          description: Solar azimuth in degrees.
        moon_alt:
          type: number
          description: Lunar altitude in degrees.
        moon_az:
          type: number
          description: Lunar azimuth in degrees.
        moon_illum:
          type: number
          description: Lunar illumination (0.0–1.0).
        field_alt:
          type: ["number", "null"]
          description: Field of view altitude in degrees.
        field_az:
          type: ["number", "null"]
          description: Field of view azimuth in degrees.
        rad_alt:
          type: ["number", "null"]
          description: Radiant altitude in degrees (zenith attraction applied).
        rad_az:
          type: ["number", "null"]
          description: Radiant azimuth in degrees.
        magn_id:
          type: ["integer", "null"]
          description: Reference to the associated magnitude observation.
        magn_solo:
          type: ["boolean", "null"]
          description: >
            `true` when this rate is the only contributor to its linked
            magnitude observation (their periods match exactly);
            `false` when the magnitude observation aggregates this rate
            together with others over a longer period; `null` when
            `magn_id` is `null`.

    MagnitudeObservation:
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier of the magnitude observation.
        shower:
          type: ["string", "null"]
          description: IAU shower code, or null for sporadic meteors.
        period_start:
          type: string
          format: date-time
          description: >
            Start of the observation period in strict ISO 8601 form
            `YYYY-MM-DDTHH:MM:SS` (UTC, no timezone marker).
          example: "2015-08-12T22:00:00"
        period_end:
          type: string
          format: date-time
          description: >
            End of the observation period in strict ISO 8601 form
            `YYYY-MM-DDTHH:MM:SS` (UTC, no timezone marker).
          example: "2015-08-12T23:00:00"
        sl_start:
          type: number
          description: Solar longitude at period start (degrees).
        sl_end:
          type: number
          description: Solar longitude at period end (degrees).
        session_id:
          type: integer
          description: Reference to the observation session.
        freq:
          type: integer
          description: Total number of observed meteors.
        mean:
          type: number
          description: Mean magnitude.
        lim_magn:
          type: ["number", "null"]
          description: Limiting magnitude.

    Session:
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier of the session.
        longitude:
          type: number
          description: Observer longitude in degrees.
        latitude:
          type: number
          description: Observer latitude in degrees.
        elevation:
          type: number
          description: Height above mean sea level in km.
        country:
          type: string
        location_name:
          type: string
          description: Name of the observation site.
        observer_id:
          type: ["integer", "null"]
        observer_name:
          type: ["string", "null"]

    MagnitudeDetail:
      type: object
      properties:
        id:
          type: integer
          description: Reference to the magnitude observation.
        magn:
          type: integer
          description: Magnitude class.
        freq:
          type: number
          description: Frequency of meteors in this magnitude class.

    Shower:
      type: object
      properties:
        iau_code:
          type: string
          description: IAU shower code.
        name:
          type: string
          description: Shower name.
        start_month:
          type: integer
        start_day:
          type: integer
        end_month:
          type: integer
        end_day:
          type: integer
        peak_month:
          type: ["integer", "null"]
        peak_day:
          type: ["integer", "null"]
        ra:
          type: ["number", "null"]
          description: Right ascension of the radiant (degrees).
        dec:
          type: ["number", "null"]
          description: Declination of the radiant (degrees).
        v:
          type: ["number", "null"]
          description: Entry velocity (km/s).
        r:
          type: ["number", "null"]
          description: Population index r.
        zhr:
          type: ["number", "null"]
          description: Zenithal hourly rate at peak.

    RatesResponse:
      type: object
      required: [observations]
      properties:
        observations:
          type: array
          items:
            $ref: '#/components/schemas/RateObservation'
        sessions:
          type: array
          description: Present when `include=sessions` was requested.
          items:
            $ref: '#/components/schemas/Session'
        magnitudes:
          type: array
          description: >
            Present when `include=magnitudes` was requested.  Contains
            the full `MagnitudeObservation` rows referenced by the
            returned rates via their `magn_id`.
          items:
            $ref: '#/components/schemas/MagnitudeObservation'
        magnitude_details:
          type: array
          description: >
            Present when `include=magnitude_details` was requested.
            Contains `MagnitudeDetail` rows (per-magnitude-class
            frequencies) for the magnitude observations referenced by
            the returned rates.
          items:
            $ref: '#/components/schemas/MagnitudeDetail'

    MagnitudesResponse:
      type: object
      required: [observations]
      properties:
        observations:
          type: array
          items:
            $ref: '#/components/schemas/MagnitudeObservation'
        sessions:
          type: array
          description: Present when `include=sessions` was requested.
          items:
            $ref: '#/components/schemas/Session'
        magnitude_details:
          type: array
          description: >
            Present when `include=magnitude_details` was requested.
            Contains `MagnitudeDetail` rows (per-magnitude-class
            frequencies) for the returned magnitude observations.
          items:
            $ref: '#/components/schemas/MagnitudeDetail'

    SessionsResponse:
      type: object
      required: [sessions]
      properties:
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/Session'
        rates:
          type: array
          description: Present when `include=rates` was requested.
          items:
            $ref: '#/components/schemas/RateObservation'
        magnitudes:
          type: array
          description: >
            Present when `include=magnitudes` was requested.  Contains
            full `MagnitudeObservation` rows for the sessions in the
            response page.
          items:
            $ref: '#/components/schemas/MagnitudeObservation'

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string

    Health:
      type: object
      required: [status, database]
      properties:
        status:
          type: string
          enum: [ok, degraded]
        database:
          type: string
          description: '"ok" when reachable, otherwise a short reason string.'

    Radiant:
      type: object
      properties:
        shower:
          type: string
          description: IAU shower code.
        month:
          type: integer
        day:
          type: integer
        ra:
          type: number
          description: Right ascension of the radiant (degrees).
        dec:
          type: number
          description: Declination of the radiant (degrees).

    StatsMeta:
      type: object
      properties:
        sessions:
          type: integer
          description: Row count of the normalized `obs_session` table.
        rates:
          type: integer
          description: Row count of the normalized `rate` table.
        magnitudes:
          type: integer
          description: Row count of the normalized `magnitude` table.
        period_start:
          type: ["string", "null"]
          format: date-time
          description: >
            Earliest `period_start` across the normalized `rate` and
            `magnitude` tables.  Strict ISO 8601 (`YYYY-MM-DDTHH:MM:SS`,
            UTC, no timezone marker).  `null` when both tables are empty.
        period_end:
          type: ["string", "null"]
          format: date-time
          description: >
            Latest `period_end` across the normalized `rate` and
            `magnitude` tables.  Same format and rules as `period_start`.
        rate_meteors:
          type: integer
          description: Sum of `freq` across all rows of `rate`.
        magnitude_meteors:
          type: integer
          description: Sum of `freq` across all rows of `magnitude`.
        rate_period_start:
          type: ["string", "null"]
          format: date-time
          description: Earliest `period_start` across all rows of `rate`.
        rate_period_end:
          type: ["string", "null"]
          format: date-time
          description: Latest `period_end` across all rows of `rate`.
        magnitude_period_start:
          type: ["string", "null"]
          format: date-time
          description: Earliest `period_start` across all rows of `magnitude`.
        magnitude_period_end:
          type: ["string", "null"]
          description: Latest `period_end` across all rows of `magnitude`.
        imported_sessions:
          type: integer
          description: Row count of the raw `imported_session` table.
        imported_rates:
          type: integer
          description: Row count of the raw `imported_rate` table.
        imported_magnitudes:
          type: integer
          description: Row count of the raw `imported_magnitude` table.
        imported_rate_meteors:
          type: integer
          description: Sum of the `number` column across all rows of `imported_rate`.
        imported_magnitude_meteors:
          type: integer
          description: >
            Sum of meteor counts across all rows of `imported_magnitude`
            (computed by summing the values of the JSON `magn` column,
            which maps magnitude class → count).

    ShowerStat:
      type: object
      properties:
        shower:
          type: ["string", "null"]
          description: IAU shower code, or null for sporadics.
        rates:
          type: integer
        magnitudes:
          type: integer

    CountryStat:
      type: object
      properties:
        country:
          type: string
        sessions:
          type: integer
        rates:
          type: integer
        magnitudes:
          type: integer

    YearStat:
      type: object
      properties:
        year:
          type: integer
        rates:
          type: integer
        magnitudes:
          type: integer

  headers:

    XTotalCount:
      description: Total number of rows matching the query, ignoring pagination.
      schema:
        type: integer

  responses:

    BadRequest:
      description: Invalid query parameter.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    ServerError:
      description: Database or server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    NoDB:
      description: No database configured.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
