openapi: 3.1.0
info:
  title: EmergencyAPI
  description: |
    Real-time Australian emergency incident data from official government feeds across all 8 states and territories, unified into a single REST API.

    EmergencyAPI aggregates and normalises emergency incident data (bushfires, floods, storms, earthquakes, rescues, and more) into a unified GeoJSON schema with CAP-aligned severity, urgency, and certainty fields. Standards-validated CAP output is available as an Atom feed via ?format=cap-atom and as per-incident documents via /incidents/{id}?format=cap-au, both validated against the OASIS CAP 1.2 schema and the CAP-AU Profile v3.0. Includes real-time incidents, proximity search, and historical audit trail snapshots.

    **Disclaimer:** EmergencyAPI provides aggregated emergency incident data for informational
    purposes only. This data is sourced from official government feeds and may be delayed,
    incomplete, or inaccurate. Do not use this API as a substitute for official emergency warnings.
  version: 1.35.0
  contact:
    name: EmergencyAPI Support
    url: https://emergencyapi.com
  license:
    name: Proprietary

servers:
  - url: https://emergencyapi.com/api/v1
    description: Production

paths:
  /incidents:
    get:
      operationId: listIncidents
      summary: List all current incidents
      description: Returns a GeoJSON FeatureCollection of all current emergency incidents, with support for filtering, pagination, and bounding box queries.
      tags: [Incidents]
      parameters:
        - name: state
          in: query
          description: "Comma-separated state codes (e.g. nsw,vic,qld)"
          schema:
            type: string
          example: nsw,vic
        - name: eventType
          in: query
          description: "Comma-separated event types (bushfire, burn_off, fire_ban, structure_fire, vehicle_fire, grass_fire, hazmat, rescue, flood, storm, tree_down, cyclone, earthquake, extreme_heat, vehicle_accident, medical, alarm, other)"
          schema:
            type: string
          example: bushfire,flood
        - name: category
          in: query
          description: "Comma-separated event categories. Groups related event types (e.g. fire includes bushfire, structure_fire, grass_fire, vehicle_fire). burn_off is its own category (planned). See /api/v1/schema for the full mapping."
          schema:
            type: string
          example: fire,weather
        - name: featureType
          in: query
          description: "Comma-separated feature types. Defaults to 'incident' (point incidents only), so boundary polygons never appear unless requested. Request them explicitly: incident_area, warning_area, fire_ban_area. See /api/v1/schema."
          schema:
            type: string
          example: warning_area
        - name: severity
          in: query
          description: "CAP-AU severity levels (Extreme, Severe, Moderate, Minor, Unknown)"
          schema:
            type: string
          example: Extreme,Severe
        - name: urgency
          in: query
          description: "CAP-AU urgency levels (Immediate, Expected, Future, Past, Unknown)"
          schema:
            type: string
          example: Immediate,Expected
        - name: certainty
          in: query
          description: "CAP-AU certainty levels (Observed, Likely, Possible, Unlikely, Unknown)"
          schema:
            type: string
          example: Observed,Likely
        - name: warningLevel
          in: query
          description: "Australian Warning System levels"
          schema:
            type: string
          example: watch_and_act,emergency_warning
        - name: status
          in: query
          description: "Incident status filter"
          schema:
            type: string
          example: active,contained
        - name: agency
          in: query
          description: "Source agency filter"
          schema:
            type: string
          example: rfs,cfs
        - name: bbox
          in: query
          description: "Bounding box: minLon,minLat,maxLon,maxLat"
          schema:
            type: string
          example: "150.0,-34.0,152.0,-33.0"
        - name: limit
          in: query
          description: "Results per page (default 100, max 500)"
          schema:
            type: integer
            default: 100
            maximum: 500
        - name: after
          in: query
          description: "Cursor for pagination (from meta.next_cursor)"
          schema:
            type: string
        - name: format
          in: query
          description: "Response format. CSV includes columns: id, state, agency, feed_id, title, event_type, status, warning_level, severity, urgency, certainty, lng, lat, address, suburb, lga, location_state, reported_at, updated_at, fetched_at, is_retracted, retracted_at, retraction_reason. cap-atom returns a standards-validated CAP Atom feed (each entry inlines a CAP-AU alert in <content>; incidents retracted in the last 7 days appear as Cancel messages). cap-au is the deprecated legacy <alerts> wrapper, kept for backward compatibility; new consumers should use cap-atom."
          schema:
            type: string
            enum: [geojson, json, csv, cap-au, cap-atom]
            default: geojson
        - name: include_retracted
          in: query
          description: "Include retracted incidents (default false). Retracted incidents are marked when upstream feeds stop publishing them."
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: A GeoJSON FeatureCollection of incidents
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/IncidentCollection'
              example:
                type: FeatureCollection
                features:
                  - type: Feature
                    id: nsw-rfs-1234567
                    geometry:
                      type: Point
                      coordinates: [150.604, -33.883]
                    properties:
                      source:
                        state: nsw
                        agency: RFS
                        feedId: "1234567"
                      title: "Bush Fire - Warragamba"
                      eventType: bushfire
                      status: active
                      warningLevel: watch_and_act
                      severity: Severe
                      urgency: Expected
                      certainty: Observed
                      location:
                        address: "Warragamba Dam Rd, Warragamba NSW"
                        suburb: Warragamba
                        state: NSW
                        latitude: -33.883
                        longitude: 150.604
                      details:
                        description: "Bush fire burning in a south-easterly direction"
                      timestamps:
                        reported: "2026-04-07T14:30:00+10:00"
                        updated: "2026-04-07T16:45:00+10:00"
                        fetched: "2026-04-07T16:46:12+10:00"
                      retraction:
                        retracted: true
                        retractedAt: "2026-04-07T18:00:00+10:00"
                        reason: upstream_removed
                meta:
                  total_count: 245
                  has_more: true
                  next_cursor: cursor_xyz789
                  generatedAt: "2026-04-07T16:46:12+10:00"
                links:
                  self: "/v1/incidents?limit=100"
                  next: "/v1/incidents?limit=100&after=cursor_xyz789"
                attribution: "https://emergencyapi.com/api/v1/attribution"
        '429':
          description: Rate limit exceeded
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
            Retry-After:
              description: Seconds until the monthly quota resets.
              schema:
                type: integer
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /incidents/{id}:
    get:
      operationId: getIncident
      summary: Get a single incident
      description: Returns a single incident as a GeoJSON Feature.
      tags: [Incidents]
      parameters:
        - name: id
          in: path
          required: true
          description: "Incident ID (e.g. nsw-rfs-1234567)"
          schema:
            type: string
        - name: format
          in: query
          description: "Response format. Default geojson returns a GeoJSON Feature. cap-au returns the incident as a single standards-valid CAP-AU <alert> document (Content-Type application/cap+xml), validated against the OASIS CAP 1.2 schema and the CAP-AU Profile v3.0."
          schema:
            type: string
            enum: [geojson, cap-au]
            default: geojson
      responses:
        '200':
          description: A GeoJSON Feature, or a CAP-AU alert document when format=cap-au
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/IncidentFeature'
            application/cap+xml:
              schema:
                type: string
                description: A single CAP-AU <alert> document (XML).
        '404':
          description: Incident not found
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /incidents/nearby:
    get:
      operationId: nearbyIncidents
      summary: Find incidents near a location
      description: Returns incidents within a radius (km) of a given lat/lng coordinate, sorted by distance.
      tags: [Incidents]
      parameters:
        - name: lat
          in: query
          required: true
          description: "Latitude"
          schema:
            type: number
          example: -33.87
        - name: lng
          in: query
          required: true
          description: "Longitude"
          schema:
            type: number
          example: 151.21
        - name: radius
          in: query
          required: true
          description: "Radius in kilometres (max 500)"
          schema:
            type: number
          example: 50
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
        - name: state
          in: query
          schema:
            type: string
        - name: eventType
          in: query
          schema:
            type: string
        - name: severity
          in: query
          schema:
            type: string
        - name: eventCategory
          in: query
          schema:
            type: string
        - name: urgency
          in: query
          schema:
            type: string
        - name: certainty
          in: query
          schema:
            type: string
        - name: warningLevel
          in: query
          schema:
            type: string
        - name: agency
          in: query
          schema:
            type: string
        - name: status
          in: query
          description: "Comma-separated incident statuses (active, contained, controlled, safe, completed). Defaults to all."
          schema:
            type: string
        - name: include_retracted
          in: query
          description: "Set true to include retracted incidents. Default false; retracted incidents are hidden, matching /incidents."
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Nearby incidents as GeoJSON FeatureCollection
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/IncidentCollection'
        '400':
          description: Missing or invalid parameters
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /incidents/snapshot:
    get:
      operationId: incidentSnapshot
      summary: Audit trail snapshot
      description: >-
        Returns the incidents that were active at a specific point in time. Recent
        datetimes are reconstructed from live and lifecycle data. Datetimes before
        2026-04-08 are reconstructed from the historical archive (4.8M+ records) and
        are gated by the same tier lookback as /incidents/history (free has no
        archive access; Starter 1 year, Developer and Business 5 years, Pro and
        Enterprise unlimited).
        Archive (pre-2026-04-08) snapshots require a state filter, and meta.source
        indicates whether the result came from "live" or "archive".
      tags: [Incidents]
      parameters:
        - name: datetime
          in: query
          required: true
          description: "ISO 8601 datetime to query (cannot be in the future)"
          schema:
            type: string
            format: date-time
          example: "2026-04-20T14:30:00+09:30"
        - name: state
          in: query
          description: "Filter by state code. Required for archive (pre-2026-04-08) snapshots."
          schema:
            type: string
          example: sa
        - name: event_type
          in: query
          description: "Filter by event type"
          schema:
            type: string
          example: bushfire
        - name: limit
          in: query
          description: "Max results (default 500)"
          schema:
            type: integer
            default: 500
            maximum: 500
      responses:
        '200':
          description: Incidents active at the requested datetime
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/IncidentCollection'
        '400':
          description: Missing or invalid datetime, or an archive (pre-2026-04-08) snapshot requested without the required state filter
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: Archive access requires a paid plan, or the datetime predates your tier's lookback window
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /incidents/history:
    get:
      operationId: listHistoricalIncidents
      summary: Historical incident data (paid tier)
      description: Returns historical emergency incidents from the 4.8M+ record archive (1840-2026). Free tier is blocked. Starter tier limited to 1 year lookback, Developer and Business to 5 years, Pro and Enterprise unlimited.
      tags: [Incidents]
      parameters:
        - name: state
          in: query
          description: "Comma-separated state codes"
          schema:
            type: string
          example: wa,sa
        - name: eventType
          in: query
          description: "Comma-separated event types"
          schema:
            type: string
          example: bushfire,flood
        - name: after
          in: query
          description: "Start of date range (ISO 8601, inclusive). Tier-limited."
          schema:
            type: string
            format: date-time
          example: "2022-01-01T00:00:00Z"
        - name: before
          in: query
          description: "End of date range (ISO 8601, exclusive). Defaults to now."
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: "Max results per page (1-500, default 100)"
          schema:
            type: integer
            default: 100
            maximum: 500
      responses:
        '200':
          description: Historical incidents matching filters
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/IncidentCollection'
        '403':
          description: Free tier blocked or depth limit exceeded
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /states:
    get:
      operationId: listStates
      summary: Feed health by state
      description: Returns the status of each state feed, including last poll time, health, and incident count.
      tags: [Meta]
      responses:
        '200':
          description: Feed status for all states
          content:
            application/json:
              schema:
                type: object
                properties:
                  states:
                    type: array
                    items:
                      type: object
                      properties:
                        state:
                          type: string
                        agency:
                          type: string
                        status:
                          type: string
                          enum: [healthy, degraded, down, unknown]
                        lastPolled:
                          type: string
                          format: date-time
                        lastSuccess:
                          type: string
                          format: date-time
                        incidentCount:
                          type: integer
                        errorCount:
                          type: integer

  /declarations/postcode/{postcode}:
    get:
      operationId: declarationsByPostcode
      summary: Disaster declaration status for a postcode
      description: "Returns the declaration STATUS for a postcode: declared, partial, activation_only, not_declared, uncertain, or unknown_postcode, with per-LGA detail and official source links. Reports declared-area status only; the billing/eligibility decision rests with the practitioner. Built for Medicare disaster telehealth checks."
      tags: [Declarations]
      parameters:
        - name: postcode
          in: path
          required: true
          schema: { type: string, pattern: '^\d{4}$' }
          example: '4380'
        - name: as_at
          in: query
          description: Point-in-time check (YYYY-MM-DD). Defaults to today.
          schema: { type: string, format: date }
        - name: include_expired
          in: query
          description: Include expired records in the per-LGA lists. Never changes matchStatus.
          schema: { type: boolean, default: false }
        - name: instrument_type
          in: query
          description: Trim the returned record lists to one instrument type. Never changes matchStatus.
          schema: { type: string, enum: [state_natural_disaster_declaration, drfa_activation_agrn, state_of_disaster_emergency_powers] }
        - name: suburb
          in: query
          description: "Optional suburb/locality name (ABS SAL) to narrow the result to the local government areas that suburb falls in, within this postcode only. Matching is exact after normalisation; there is no fuzzy matching. An unmatched suburb never fails the lookup: the full-postcode result is returned with narrowing.applied=false, a reason, and the postcode's known suburbs. Narrowing can legitimately change matchStatus (for example partial to declared); a suburb that itself spans two LGAs stays partial, and a bare name that denotes suburbs on both sides of a border (Mingoola in 4380) matches all of them."
          schema: { type: string }
          example: 'Stanthorpe'
      responses:
        '200':
          description: Declaration status (including unknown_postcode for an absent postcode)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PostcodeDeclarationResult' }
        '400':
          description: Malformed postcode or as_at
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '403':
          description: The account lacks the declarations entitlement (enterprise-only, manually negotiated)
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '429':
          description: Rate limit exceeded
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
            Retry-After:
              description: Seconds until the monthly quota resets.
              schema:
                type: integer
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }

  /declarations/point:
    post:
      operationId: declarationsByPoint
      summary: Disaster declaration status for an exact point
      description: "Returns the declaration STATUS for the local government area containing a lat/lng point, resolved against ABS LGA 2022 (ASGS Edition 3) boundary polygons at full resolution. POST with a JSON body is used deliberately so coordinates never appear in URLs or request logs; the request is read-only and safe to retry. Coordinates are never stored, logged, or echoed back, and the response is Cache-Control: no-store. Reports declared-area status only; the billing decision rests with the practitioner. A point outside every Australian LGA returns unknown_location. A point exactly on a shared LGA boundary returns every covering area with onBoundary=true. LGA matching is at ABS code level; some ABS codes aggregate multiple administrative areas (for example Unincorporated NSW covers both Far West and Lord Howe Island)."
      tags: [Declarations]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PointLookupRequest' }
      responses:
        '200':
          description: Declaration status for the covering LGA (including unknown_location when no LGA covers the point)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PointDeclarationResult' }
        '400':
          description: Invalid body, lat, lng, or as_at. Error detail never repeats submitted values.
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '403':
          description: The account lacks the declarations entitlement (enterprise-only, manually negotiated)
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '429':
          description: Rate limit exceeded
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
            Retry-After:
              description: Seconds until the monthly quota resets.
              schema:
                type: integer
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }

  /declarations:
    get:
      operationId: listDeclarations
      summary: List/filter declarations
      description: "Browse and historically report on declaration records. Filters and keyset (cursor) pagination. Record status is derived at query time."
      tags: [Declarations]
      parameters:
        - name: jurisdiction
          in: query
          schema: { type: string, example: nsw }
        - name: status
          in: query
          schema: { type: string, enum: [active, expired, all], default: active }
        - name: instrument_type
          in: query
          schema: { type: string }
        - name: disaster_type
          in: query
          schema: { type: string }
        - name: active_on
          in: query
          schema: { type: string, format: date }
        - name: limit
          in: query
          schema: { type: integer, default: 100, maximum: 500 }
        - name: cursor
          in: query
          description: Opaque pagination cursor from links.next.
          schema: { type: string }
      responses:
        '200':
          description: A page of declaration records
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DeclarationList' }
        '400':
          description: Invalid active_on date, status, limit, or cursor
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '403':
          description: The account lacks the declarations entitlement (enterprise-only, manually negotiated)
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '429':
          description: Rate limit exceeded
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
            Retry-After:
              description: Seconds until the monthly quota resets.
              schema:
                type: integer
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }

  /declarations/agrn/{agrn}:
    get:
      operationId: declarationByAgrn
      summary: One disaster event by AGRN (across sources)
      description: "The declaration/activation for an Australian Government Reference Number, merged across sources with the full affected-LGA list."
      tags: [Declarations]
      parameters:
        - name: agrn
          in: path
          required: true
          schema: { type: string, pattern: '^\d{1,6}$' }
          example: '1284'
      responses:
        '200':
          description: The merged declaration record
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DeclarationDetail' }
        '400':
          description: Malformed AGRN (must be numeric)
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '403':
          description: The account lacks the declarations entitlement (enterprise-only, manually negotiated)
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '404':
          description: No declaration found for the AGRN
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }
        '429':
          description: Rate limit exceeded
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
            Retry-After:
              description: Seconds until the monthly quota resets.
              schema:
                type: integer
          content:
            application/problem+json:
              schema: { $ref: '#/components/schemas/ProblemDetails' }

  /status:
    get:
      operationId: apiStatus
      summary: API health check
      description: Returns API health, version, uptime, and a summary of feed statuses.
      tags: [Meta]
      responses:
        '200':
          description: API health status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  version:
                    type: string
                    example: 1.16.0
                  uptime:
                    type: string
                    example: 86400s
                  timestamp:
                    type: string
                    format: date-time
                  counts:
                    type: object
                    description: "Summary of feed statuses. 'stale' is derived from last_polled_at age (a feed not polled within 2 hours reads as stale, so a dead poller is visible to monitors)."
                    properties:
                      total:
                        type: integer
                      healthy:
                        type: integer
                      degraded:
                        type: integer
                      down:
                        type: integer
                      stale:
                        type: integer
                        description: "Feeds not polled within 2 hours, derived from last_polled_at age regardless of stored status."
                      unknown:
                        type: integer
                  feeds:
                    type: array
                    items:
                      type: object
                      properties:
                        state:
                          type: string
                        agency:
                          type: string
                        status:
                          type: string
                          enum: [healthy, degraded, down, stale, unknown]

  /attribution:
    get:
      operationId: getAttribution
      summary: Data source attributions
      description: Returns attribution information for all data sources, including licence details. Required for CC BY compliance.
      tags: [Meta]
      responses:
        '200':
          description: Attribution data for all sources
          content:
            application/json:
              schema:
                type: object
                properties:
                  description:
                    type: string
                  disclaimer:
                    type: string
                  count:
                    type: integer
                    description: "Number of sources returned"
                  sources:
                    type: array
                    items:
                      type: object
                      properties:
                        state:
                          type: string
                        agency:
                          type: string
                        name:
                          type: string
                        licence:
                          type: string
                          description: "The published licence. Only recognised CC licences or an explicit 'Used with permission' statement are shown; anything else (internal working notes, unconfirmed sources) is reported as 'Licence under review — see https://emergencyapi.com/compliance'."
                        url:
                          type: string
                        official:
                          type: boolean
                          description: "true for official government/statutory feeds, false for community-decoded or unverified sources."
                        attribution:
                          type: string
                          description: "Agency-specified attribution text, present only when the source requires it."

  /events:
    get:
      operationId: listEvents
      summary: List clustered events (incident intelligence)
      description: |
        Returns spatially clustered emergency events as a GeoJSON FeatureCollection.
        Events group related incidents using PostGIS ST_ClusterDBSCAN spatial clustering.
        Each event has a boundary polygon, centroid, affected suburbs, contributing agencies,
        and auto-generated title and summary. Only active events are returned by default.
      tags: [Events]
      parameters:
        - name: limit
          in: query
          description: "Maximum events to return (default 50, max 200)"
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: state
          in: query
          description: "Comma-separated state codes to filter by (e.g. nsw,vic). Uses array overlap matching."
          schema:
            type: string
          example: nsw,vic
        - name: eventType
          in: query
          description: "Comma-separated event types (e.g. bushfire,flood)"
          schema:
            type: string
          example: bushfire,flood
        - name: severity
          in: query
          description: "Comma-separated max severity levels"
          schema:
            type: string
          example: Extreme,Severe
        - name: warningLevel
          in: query
          description: "Comma-separated max warning levels"
          schema:
            type: string
          example: emergency_warning,watch_and_act
      responses:
        '200':
          description: A GeoJSON FeatureCollection of clustered events
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/EventCollection'
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: Rate limit exceeded
          headers:
            RateLimit-Limit:
              schema:
                type: integer
            RateLimit-Remaining:
              schema:
                type: integer
            RateLimit-Reset:
              schema:
                type: integer
            Retry-After:
              description: Seconds until the monthly quota resets.
              schema:
                type: integer
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /events/{id}:
    get:
      operationId: getEvent
      summary: Get a single event with optional linked incidents
      description: |
        Returns a single clustered event by ID. Use `include_incidents=true` to also
        retrieve all incidents that belong to this event cluster.
      tags: [Events]
      parameters:
        - name: id
          in: path
          required: true
          description: Event UUID
          schema:
            type: string
            format: uuid
        - name: include_incidents
          in: query
          description: "Include linked incidents in the response"
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: A single event as a GeoJSON Feature
          content:
            application/geo+json:
              schema:
                $ref: '#/components/schemas/EventFeature'
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '404':
          description: Event not found
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /schema:
    get:
      operationId: getSchema
      summary: API schema and valid enum values
      description: Returns all valid enum values for event types, categories, states, warning levels, and other filterable fields. Use this to discover what values are accepted by filter parameters. No authentication required.
      tags: [Meta]
      responses:
        '200':
          description: All valid enum values and category mappings
          content:
            application/json:
              schema:
                type: object
                properties:
                  eventTypes:
                    type: array
                    items:
                      type: string
                  eventCategories:
                    type: array
                    items:
                      type: string
                  categoryMapping:
                    type: object
                    description: "Maps each category to its constituent event types"
                  states:
                    type: array
                    items:
                      type: string
                  warningLevels:
                    type: array
                    items:
                      type: string
                  statuses:
                    type: array
                    items:
                      type: string
                  severities:
                    type: array
                    items:
                      type: string
                  urgencies:
                    type: array
                    items:
                      type: string
                  certainties:
                    type: array
                    items:
                      type: string
                  formats:
                    type: array
                    items:
                      type: string

  /gauges:
    get:
      operationId: listGauges
      summary: River gauge stations with live flood status
      description: >-
        Returns river gauge stations with their latest water level, the Bureau of
        Meteorology flood classification thresholds for each gauge, and a derived
        floodClass (below_minor, minor, moderate, major, or unclassified) computed
        from the latest reading against the published thresholds. Readings are in
        metres relative to each gauge's local datum. readingStatus flags stale data
        (bom-kiwis sourced stations are a 1-2 day archive, stale horizon 72 hours;
        telemetry sources 24 hours). Status reporting, not flood prediction.
      tags: [Gauges]
      parameters:
        - name: state
          in: query
          description: "Lowercase state code (nsw, vic, qld, sa, wa, tas, nt, act)"
          schema:
            type: string
          example: nsw
        - name: floodClass
          in: query
          description: "Filter to gauges at or above this class (>= semantics: minor matches moderate and major too)"
          schema:
            type: string
            enum: [minor, moderate, major]
        - name: lga
          in: query
          description: "ABS LGA code (the same LGA_2022 vocabulary as the declarations API)"
          schema:
            type: string
          example: "17750"
        - name: source
          in: query
          description: "Polling source (hydstra-nsw, hydstra-vic, hydstra-qld, aquarius-sa, aquarius-tas, aquarius-nt, bom-kiwis)"
          schema:
            type: string
        - name: bbox
          in: query
          description: "Bounding box minLng,minLat,maxLng,maxLat (within Australia)"
          schema:
            type: string
          example: "138.4,-35.4,139.0,-34.6"
        - name: limit
          in: query
          description: "Max results per page (1-500, default 100)"
          schema:
            type: integer
            default: 100
            maximum: 500
        - name: cursor
          in: query
          description: "Pagination cursor from meta.nextCursor"
          schema:
            type: string
      responses:
        '200':
          description: Gauge stations with latest readings and flood status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GaugeCollection'
        '401':
          description: Missing or invalid API key
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /gauges/{id}:
    get:
      operationId: getGauge
      summary: One gauge with a 6-hour recent window
      description: >-
        Returns a single gauge station plus its trailing 6 hours of raw readings
        (enough to see the current trend). Deeper history is the tier-gated
        /gauges/{id}/readings endpoint.
      tags: [Gauges]
      parameters:
        - name: id
          in: path
          required: true
          description: "Gauge id from /gauges (e.g. hydstra-nsw-410001)"
          schema:
            type: string
      responses:
        '200':
          description: The gauge and its recent readings
          content:
            application/json:
              schema:
                type: object
                properties:
                  gauge:
                    $ref: '#/components/schemas/Gauge'
                  recent:
                    type: array
                    items:
                      $ref: '#/components/schemas/GaugeReading'
        '404':
          description: No active gauge with that id
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /gauges/{id}/readings:
    get:
      operationId: listGaugeReadings
      summary: Gauge reading history (paid tier)
      description: >-
        Historical water level readings for one gauge. Free tier is blocked (the
        free surface is current readings on /gauges and the 6-hour window on
        /gauges/{id}). Starter tier limited to 1 year lookback, Developer and
        Business to 5 years, Pro and Enterprise unlimited. Retention ladder: raw
        30 days, hourly rollups 90 days, daily forever — plus full-resolution raw
        inside flood-event windows kept forever. interval=auto picks the finest
        granularity available for the requested window.
      tags: [Gauges]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: after
          in: query
          description: "Start of range (ISO 8601, inclusive). Tier-limited."
          schema:
            type: string
            format: date-time
        - name: before
          in: query
          description: "End of range (ISO 8601, exclusive). Defaults to now."
          schema:
            type: string
            format: date-time
        - name: interval
          in: query
          description: "raw, hourly, daily, or auto (default)"
          schema:
            type: string
            enum: [raw, hourly, daily, auto]
            default: auto
        - name: above
          in: query
          description: "Only readings at/above this published flood classification for the gauge"
          schema:
            type: string
            enum: [minor, moderate, major]
        - name: limit
          in: query
          schema:
            type: integer
            default: 500
            maximum: 1000
        - name: cursor
          in: query
          description: "Pagination cursor from meta.nextCursor (ISO timestamp keyset)"
          schema:
            type: string
      responses:
        '200':
          description: Readings for the gauge, newest first
          content:
            application/json:
              schema:
                type: object
                properties:
                  gaugeId:
                    type: string
                  interval:
                    type: string
                  readings:
                    type: array
                    items:
                      $ref: '#/components/schemas/GaugeReading'
        '403':
          description: Free tier blocked or depth limit exceeded
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /gauges/summary:
    get:
      operationId: getGaugeSummary
      summary: Flood status counts by state or LGA
      description: >-
        Counts of gauges at or above minor/moderate/major flood classification,
        grouped by state or LGA, plus the full list of at-or-above-minor stations
        (capped at 500). STALE GUARD: only readings within the staleness horizon
        enter class counts — a days-old reading above minor is counted as stale,
        never as a current flood signal.
      tags: [Gauges]
      parameters:
        - name: groupBy
          in: query
          description: "state (default) or lga (requires the state parameter)"
          schema:
            type: string
            enum: [state, lga]
            default: state
        - name: state
          in: query
          schema:
            type: string
          example: sa
      responses:
        '200':
          description: Flood status summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  summary:
                    type: object
                    properties:
                      total:
                        type: integer
                      minor:
                        type: integer
                      moderate:
                        type: integer
                      major:
                        type: integer
                      stale:
                        type: integer
                      unclassified:
                        type: integer
                      atOrAboveMinor:
                        type: integer
                  groups:
                    type: array
                    items:
                      type: object
                  aboveMinorStations:
                    type: array
                    items:
                      $ref: '#/components/schemas/Gauge'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key in format: eak_live_... . Keys work from any origin by default.
        A key can optionally be restricted to specific browser origins in the
        dashboard; a request from a browser origin not on the allowlist is
        rejected with 403. Requests without an Origin header (server-side) are
        never blocked by this.

  schemas:
    IncidentFeature:
      type: object
      properties:
        type:
          type: string
          const: Feature
        id:
          type: string
          example: nsw-rfs-1234567
        geometry:
          type: object
          properties:
            type:
              type: string
              enum: [Point, Polygon, MultiPolygon]
            coordinates:
              oneOf:
                - type: array
                  items:
                    type: number
                  description: "[longitude, latitude] for Point"
                - type: array
                  items:
                    type: array
                    items:
                      type: array
                      items:
                        type: number
                  description: "Ring arrays for Polygon"
        properties:
          type: object
          properties:
            source:
              type: object
              properties:
                state:
                  type: string
                agency:
                  type: string
                feedId:
                  type: string
            title:
              type: string
            eventType:
              type: string
              enum: [bushfire, burn_off, fire_ban, structure_fire, vehicle_fire, grass_fire, hazmat, rescue, flood, storm, tree_down, cyclone, earthquake, extreme_heat, vehicle_accident, medical, alarm, other]
            eventCategory:
              type: string
              enum: [fire, weather, rescue, hazmat, medical, alarm, planned, earth, hazard, other]
              description: "Groups related event types. See /api/v1/schema for the full mapping."
            featureType:
              type: string
              enum: [incident, incident_area, warning_area, fire_ban_area]
              description: "Point incident or boundary polygon. Defaults to 'incident'; list endpoints return only incidents unless ?featureType= is supplied."
            status:
              type: string
              enum: [active, contained, controlled, safe, completed]
            warningLevel:
              type: string
              enum: [advice, watch_and_act, emergency_warning, none]
            severity:
              type: string
              enum: [Extreme, Severe, Moderate, Minor, Unknown]
            urgency:
              type: string
              enum: [Immediate, Expected, Future, Past, Unknown]
            certainty:
              type: string
              enum: [Observed, Likely, Possible, Unlikely, Unknown]
            location:
              type: object
              properties:
                address:
                  type: string
                suburb:
                  type: string
                state:
                  type: string
                lga:
                  type: string
                latitude:
                  type: number
                  description: "Centroid latitude (extracted from geometry)"
                longitude:
                  type: number
                  description: "Centroid longitude (extracted from geometry)"
                precision:
                  type: string
                  enum: [exact, postcode, lga]
                  description: "Coordinate precision. 'exact' = authoritative point from the source feed. 'postcode' or 'lga' = approximate centroid backfilled for historical archive rows that logged no point. Absent when no coordinates are available (geometry is null)."
            details:
              type: object
              description: >-
                Agency-specific incident metadata, passed through from the source feed. OPTIONAL and
                varies by feed AND by incident (it is not standardised across agencies, and some feeds
                carry none). Render whatever keys are present rather than expecting a fixed set. The
                properties below are the common ones; the full per-feed breakdown (every field, its
                type and meaning) is documented at
                https://emergencyapi.com/guides/incident-details-fields
              properties:
                description:
                  type: string
                  description: "Source detail text; structure varies by feed (NSW RFS alert block, a place name, raw pager text, etc.)."
                resources:
                  type: integer
                  description: "Count of resources (crews/appliances/units) assigned or responding."
                aircraft:
                  type: integer
                  description: "Count of aircraft assigned (SA CFS/MFS)."
                decoded_units:
                  type: array
                  items:
                    type: string
                  description: "Decoded responding unit names (SA pager feeds)."
                alarm_level_description:
                  type: string
                  description: "Alarm/warning level label (SA pager feeds)."
                priority_description:
                  type: string
                  description: "Case priority label (SA Ambulance pager)."
                size:
                  type: string
                  description: "Incident size label, e.g. Small/Medium/Large (VIC OSOM)."
                cad_id:
                  type: string
                  description: "CAD dispatch identifier (e.g. WA DFES 6-digit number)"
                source_id:
                  type: string
                  description: "Upstream agency record ID"
                source_url:
                  type: string
                  format: uri
                  description: "Deep link to the incident on the source agency website"
              additionalProperties: true
            timestamps:
              type: object
              properties:
                reported:
                  type: string
                  format: date-time
                updated:
                  type: string
                  format: date-time
                fetched:
                  type: string
                  format: date-time
            archive:
              type: object
              description: "EM-DAT / historical enrichment. Present only on deep-archive snapshot results (meta.source = archive). Each field appears only when populated."
              properties:
                eventStartDate:
                  type: string
                  format: date
                eventEndDate:
                  type: string
                  format: date
                magnitude:
                  type: number
                magnitudeScale:
                  type: string
                impactDeaths:
                  type: integer
                impactAffected:
                  type: integer
                impactEconomic:
                  type: number
                glideNumber:
                  type: string
                  description: "GLIDE disaster identifier"

    IncidentCollection:
      type: object
      properties:
        type:
          type: string
          const: FeatureCollection
        features:
          type: array
          items:
            $ref: '#/components/schemas/IncidentFeature'
        meta:
          type: object
          properties:
            total_count:
              type: integer
            has_more:
              type: boolean
            next_cursor:
              type: string
            generatedAt:
              type: string
              format: date-time
        links:
          type: object
          properties:
            self:
              type: string
            next:
              type: string
        attribution:
          type: string

    EventFeature:
      type: object
      properties:
        type:
          type: string
          const: Feature
        id:
          type: string
          format: uuid
          example: fb6e6418-3edb-40ac-83c3-1afb2550e887
        geometry:
          type: object
          description: "Boundary polygon (convex hull of cluster) or centroid point"
          properties:
            type:
              type: string
              enum: [Polygon, Point]
            coordinates:
              oneOf:
                - type: array
                  items:
                    type: array
                    items:
                      type: array
                      items:
                        type: number
                  description: "Ring arrays for Polygon boundary"
                - type: array
                  items:
                    type: number
                  description: "[longitude, latitude] for Point centroid"
        properties:
          type: object
          properties:
            title:
              type: string
              example: "Cherry Gardens bushfire"
            incidentCount:
              type: integer
              example: 42
            affectedSuburbs:
              type: array
              items:
                type: string
              example: ["Cherry Gardens", "Lobethal", "Woodside"]
            affectedStates:
              type: array
              items:
                type: string
              example: ["sa"]
            agencies:
              type: array
              items:
                type: string
              example: ["CFS", "MFS", "GA-HOTSPOTS"]
            eventTypes:
              type: array
              items:
                type: string
              example: ["bushfire"]
            maxSeverity:
              type: string
              enum: [Extreme, Severe, Moderate, Minor, Unknown]
            maxWarningLevel:
              type: string
              enum: [advice, watch_and_act, emergency_warning, none]
            estimatedHectares:
              type: number
              nullable: true
              example: 2400.5
            summary:
              type: string
              example: "42 incidents from CFS, MFS, GA-HOTSPOTS affecting Cherry Gardens, Lobethal, Woodside."
            firstDetectedAt:
              type: string
              format: date-time
              nullable: true
            lastUpdatedAt:
              type: string
              format: date-time
            isActive:
              type: boolean

    EventCollection:
      type: object
      properties:
        type:
          type: string
          const: FeatureCollection
        features:
          type: array
          items:
            $ref: '#/components/schemas/EventFeature'
        meta:
          type: object
          properties:
            totalEvents:
              type: integer
            generatedAt:
              type: string
              format: date-time
        attribution:
          type: string

    ProblemDetails:
      type: object
      description: "RFC 9457 Problem Details"
      properties:
        type:
          type: string
          example: "https://emergencyapi.com/errors/rate-limit-exceeded"
        title:
          type: string
          example: Rate Limit Exceeded
        status:
          type: integer
          example: 429
        detail:
          type: string
        instance:
          type: string

    DeclarationRecord:
      type: object
      properties:
        agrn: { type: string, nullable: true }
        title: { type: string }
        instrumentType: { type: string, enum: [state_natural_disaster_declaration, drfa_activation_agrn, state_of_disaster_emergency_powers] }
        jurisdiction: { type: string }
        disasterTypes: { type: array, items: { type: string } }
        startDate: { type: string, format: date, nullable: true }
        endDate: { type: string, format: date, nullable: true }
        status: { type: string, enum: [active, expired, unknown] }
        source: { type: string, description: Display name of the highest-precedence reporting source }
        sources: { type: array, items: { type: string }, description: "Every government source reporting this AGRN+instrument for this LGA. Multiple entries = multiple sources corroborate it." }
        sourceUrl: { type: string }
        sourcePublishedAt: { type: string, format: date-time, nullable: true }
    PostcodeDeclarationRecord:
      allOf:
        - $ref: '#/components/schemas/DeclarationRecord'
        - type: object
          properties:
            currencyConfidence: { type: string, enum: [strong, moderate, weak], nullable: true, description: "TCDE currency-confidence band for an in-force record (null when not in force). Transparent evidence, never a numeric 'chance you can bill' score." }
            basis: { type: string, description: "Short human-readable basis for this record's verdict (the evidence behind its status)." }
    MatchedLga:
      type: object
      properties:
        lgaCode: { type: string }
        lgaName: { type: string }
        populationShare: { type: number, nullable: true, description: "Share of the postcode's population in this LGA (ABS correspondence). Always the postcode-level share, even when the response is narrowed by suburb (never renormalised). null for point lookups, where population share is not a meaningful concept." }
        declarationStatus: { type: string, enum: [declared, uncertain, activation_only, not_declared] }
        declarations: { type: array, items: { $ref: '#/components/schemas/PostcodeDeclarationRecord' } }
    SuburbNarrowing:
      type: object
      description: Present on postcode responses only when the suburb parameter was supplied.
      properties:
        requested: { type: string, description: The trimmed caller input (up to 100 characters) }
        applied: { type: boolean }
        method: { type: string, enum: [spatial_derivation, abs_sal_lga_correspondence], description: "How suburb-to-LGA membership was derived. spatial_derivation = area-weighted intersection of ABS SAL 2021 and LGA 2022 boundary polygons (the current source; ABS publishes no SAL 2021 to LGA 2022 correspondence); abs_sal_lga_correspondence = a published ABS population-weighted correspondence, if one is adopted later." }
        matchedSuburbs:
          type: array
          items: { type: object, properties: { salCode: { type: string }, salName: { type: string } } }
          description: The ABS SAL suburb(s) the input matched within this postcode. More than one entry means the bare name denotes multiple suburbs (their LGA sets are unioned).
        reason: { type: string, enum: [suburb_not_found_in_postcode, suburb_lgas_outside_postcode, unknown_postcode], description: Present only when applied is false }
        knownSuburbs: { type: array, items: { type: string }, description: Present only when applied is false and the postcode is known — the postcode's suburb names, for typo recovery }
    PostcodeDeclarationResult:
      type: object
      properties:
        postcode: { type: string }
        matchStatus: { type: string, enum: [declared, partial, not_declared, uncertain, activation_only, unknown_postcode] }
        matchStatusReason: { type: string, description: Present when matchStatus is uncertain or activation_only }
        matchedLgas: { type: array, items: { $ref: '#/components/schemas/MatchedLga' } }
        narrowing: { $ref: '#/components/schemas/SuburbNarrowing' }
        asAt: { type: string, format: date, description: Date the status was evaluated against; the requested as_at value or today (AEST) when omitted }
        dataLastUpdated: { type: string, format: date-time, nullable: true }
        stale: { type: boolean }
        disclaimer: { type: string }
        attribution: { type: string }
    PointLookupRequest:
      type: object
      required: [lat, lng]
      properties:
        lat: { type: number, minimum: -90, maximum: 90 }
        lng: { type: number, minimum: -180, maximum: 180 }
        as_at: { type: string, format: date, description: Point-in-time check (YYYY-MM-DD). Defaults to today. }
        include_expired: { type: boolean, default: false, description: Include expired records in the per-LGA lists. Never changes matchStatus. }
        instrument_type: { type: string, enum: [state_natural_disaster_declaration, drfa_activation_agrn, state_of_disaster_emergency_powers], description: Trim the returned record lists to one instrument type. Never changes matchStatus. }
    PointDeclarationResult:
      type: object
      properties:
        matchStatus: { type: string, enum: [declared, partial, not_declared, uncertain, activation_only, unknown_location] }
        matchStatusReason: { type: string, description: Present when matchStatus is uncertain or activation_only }
        matchedLgas:
          type: array
          items: { $ref: '#/components/schemas/MatchedLga' }
          description: "Normally one area. Two areas only when the point lies exactly on a shared boundary (onBoundary=true); partial is then possible and means the covering areas have different statuses."
        onBoundary: { type: boolean, description: Present (true) only when the point lies on a shared LGA boundary and more than one area is returned. }
        boundaryEdition: { type: string, example: LGA_2022, description: "ABS boundary edition the point was resolved against. Informative, not an enum — it changes with ASGS edition refreshes. LGA matching is at ABS code level; point-in-polygon cannot be more precise than the code." }
        asAt: { type: string, format: date, description: Date the status was evaluated against; the requested as_at value or today (AEST) when omitted }
        dataLastUpdated: { type: string, format: date-time, nullable: true }
        stale: { type: boolean }
        disclaimer: { type: string }
        attribution: { type: string }
    DeclarationListItem:
      allOf:
        - $ref: '#/components/schemas/DeclarationRecord'
        - type: object
          properties:
            lgaCount: { type: integer }
    DeclarationList:
      type: object
      properties:
        declarations: { type: array, items: { $ref: '#/components/schemas/DeclarationListItem' } }
        dataLastUpdated: { type: string, format: date-time, nullable: true }
        stale: { type: boolean }
        disclaimer: { type: string }
        links: { type: object, properties: { next: { type: string, nullable: true } } }
        attribution: { type: string }
    DeclarationDetail:
      type: object
      properties:
        agrn: { type: string }
        title: { type: string }
        instrumentType: { type: string }
        jurisdiction: { type: string }
        disasterTypes: { type: array, items: { type: string } }
        startDate: { type: string, format: date, nullable: true }
        endDate: { type: string, format: date, nullable: true }
        status: { type: string, enum: [active, expired, unknown] }
        source: { type: string }
        sourcePublishedAt: { type: string, format: date-time, nullable: true }
        sourceUrl: { type: string }
        contributingSources: { type: array, items: { type: object, properties: { source: { type: string }, instrumentType: { type: string }, sourceUrl: { type: string } } } }
        affectedLgas: { type: array, items: { type: object, properties: { lgaCode: { type: string, nullable: true }, lgaName: { type: string } } } }
        dataLastUpdated: { type: string, format: date-time, nullable: true }
        stale: { type: boolean }
        disclaimer: { type: string }
        attribution: { type: string }

    Gauge:
      type: object
      description: "A river gauge station with its latest reading and derived flood status. Readings are metres relative to the gauge's local datum, not sea level."
      properties:
        id: { type: string, example: hydstra-nsw-410001 }
        name: { type: string }
        river: { type: string, nullable: true }
        state: { type: string, example: NSW }
        agency: { type: string, nullable: true }
        source: { type: string }
        awrcStationNo: { type: string, nullable: true }
        coordinates: { type: object, properties: { lat: { type: number }, lng: { type: number } } }
        lga: { type: object, nullable: true, properties: { code: { type: string }, name: { type: string, nullable: true } } }
        latest: { type: object, nullable: true, properties: { value: { type: number }, observedAt: { type: string, format: date-time }, quality: { type: string, nullable: true } } }
        readingStatus: { type: string, enum: [live, stale] }
        floodClass: { type: string, enum: [below_minor, minor, moderate, major, unclassified], description: "Derived at read time from the latest reading vs the published BoM thresholds; never stored as judgement." }
        thresholds: { type: object, properties: { minor: { type: number, nullable: true }, moderate: { type: number, nullable: true }, major: { type: number, nullable: true }, unit: { type: string }, source: { type: string, nullable: true }, version: { type: string, nullable: true }, fetchedAt: { type: string, format: date-time, nullable: true } } }
        datum: { type: object, nullable: true, properties: { gaugeZeroM: { type: number, nullable: true }, datum: { type: string, nullable: true } } }

    GaugeReading:
      type: object
      description: "A raw reading (observedAt/value/quality) or an aggregated bucket (bucket/min/max/mean/nObs), depending on the interval."
      properties:
        observedAt: { type: string, format: date-time }
        value: { type: number }
        quality: { type: string, nullable: true }
        bucket: { type: string }
        min: { type: number }
        max: { type: number }
        mean: { type: number }
        nObs: { type: integer }

    GaugeCollection:
      type: object
      properties:
        gauges: { type: array, items: { $ref: '#/components/schemas/Gauge' } }
        meta: { type: object, properties: { count: { type: integer }, hasMore: { type: boolean }, nextCursor: { type: string }, generatedAt: { type: string, format: date-time } } }
        disclaimer: { type: string }
        attribution: { type: string }

security:
  - BearerAuth: []

tags:
  - name: Incidents
    description: Emergency incident data
  - name: Declarations
    description: "Australian disaster declaration and DRFA funding-activation status by postcode and LGA. Reports declared-area status; the practitioner makes the billing/eligibility decision."
  - name: Events
    description: "Spatially clustered incident intelligence. Groups related incidents into events with boundary polygons, affected areas, and structured summaries."
  - name: Gauges
    description: "River gauge observations against the Bureau of Meteorology flood classifications. Status reporting, not flood prediction, and not a substitute for official warnings."
  - name: Meta
    description: API metadata and health
