{"openapi":"3.1.0","info":{"title":"Aeroza","description":"Programmable weather intelligence: streaming APIs, geospatial queries, and probabilistic nowcasting.","version":"0.1.0"},"paths":{"/health":{"get":{"tags":["meta"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}},"/":{"get":{"tags":["meta"],"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Root  Get"}}}}}}},"/v1/alerts":{"get":{"tags":["alerts"],"summary":"List active NWS alerts","description":"Returns currently-active NWS alerts as a GeoJSON FeatureCollection. Filter by a single point (alerts whose polygon intersects), or by a bounding box (``min_lng,min_lat,max_lng,max_lat``), and/or by minimum severity. ``point`` and ``bbox`` are mutually exclusive — supplying both is an error. Results are ordered by severity descending then earliest expiry.","operationId":"list_alerts_v1_alerts_get","parameters":[{"name":"point","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to alerts intersecting 'lat,lng'","examples":["29.76,-95.37"],"title":"Point"},"description":"Filter to alerts intersecting 'lat,lng'"},{"name":"bbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to alerts intersecting 'min_lng,min_lat,max_lng,max_lat'","examples":["-95.7,29.5,-95.0,30.0"],"title":"Bbox"},"description":"Filter to alerts intersecting 'min_lng,min_lat,max_lng,max_lat'"},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Severity"},{"type":"null"}],"description":"Return only alerts at this severity level or higher","title":"Severity"},"description":"Return only alerts at this severity level or higher"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results to return (default 100)","default":100,"title":"Limit"},"description":"Max results to return (default 100)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertFeatureCollection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/alerts/stream":{"get":{"tags":["alerts"],"summary":"Stream new NWS alerts (Server-Sent Events)","operationId":"stream_alerts_v1_alerts_stream_get","responses":{"200":{"description":"An SSE stream where each ``event: alert`` carries an alert payload (NWS-aliased JSON) in ``data:``. The connection stays open; clients should reconnect with ``Last-Event-ID`` if they want resume semantics (not yet honored on the server side).","content":{"text/event-stream":{}}},"503":{"description":"Streaming is not available (NATS broker unreachable)."}}}},"/v1/alerts/historical":{"get":{"tags":["alerts"],"summary":"List historical NWS Storm-Based Warnings (IEM archive)","description":"Returns NWS Storm-Based Warnings (tornado, severe thunderstorm, flash flood, marine, etc.) issued during the supplied UTC window, filtered to one or more NWS forecast offices (``wfos``). Powered by the Iowa Environmental Mesonet archive, which retains every warning polygon back to 2002 — well beyond NWS's own ~30-day retention. Returns the same GeoJSON ``FeatureCollection`` shape as ``GET /v1/alerts`` so the same map renderer works.","operationId":"list_historical_alerts_v1_alerts_historical_get","parameters":[{"name":"since","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"Inclusive UTC window start (ISO-8601, with Z or offset)","examples":["2024-05-16T22:00:00Z"],"title":"Since"},"description":"Inclusive UTC window start (ISO-8601, with Z or offset)"},{"name":"until","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"Exclusive UTC window end (ISO-8601, with Z or offset)","examples":["2024-05-17T02:30:00Z"],"title":"Until"},"description":"Exclusive UTC window end (ISO-8601, with Z or offset)"},{"name":"wfos","in":"query","required":true,"schema":{"type":"string","minLength":3,"description":"Comma-separated NWS forecast office 3-letter codes (e.g. 'HGX,LCH'). At least one is required so we don't ship CONUS-wide history through one process.","examples":["HGX,LCH"],"title":"Wfos"},"description":"Comma-separated NWS forecast office 3-letter codes (e.g. 'HGX,LCH'). At least one is required so we don't ship CONUS-wide history through one process."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertFeatureCollection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/alerts/{alert_id}":{"get":{"tags":["alerts"],"summary":"Get a single NWS alert (full detail)","description":"Returns one alert as a GeoJSON Feature including the long-form ``description`` and ``instruction`` fields that the list endpoint omits. Includes alerts whose ``expires`` is in the past.","operationId":"get_alert_v1_alerts__alert_id__get","parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"Alert id (often a URN, e.g. 'urn:oid:2.49.0.1.840.0.…')","title":"Alert Id"},"description":"Alert id (often a URN, e.g. 'urn:oid:2.49.0.1.840.0.…')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertDetailFeature"}}}},"404":{"description":"Alert not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/mrms/files":{"get":{"tags":["mrms"],"summary":"List MRMS files in the catalog","description":"Returns the most-recent rows of the MRMS file catalog populated by the ``aeroza-ingest-mrms`` worker. Filter by ``product`` (e.g. ``MergedReflectivityComposite``), ``level`` (e.g. ``00.50``), and a half-open ``[since, until)`` window on ``valid_at``. Results are ordered by ``valid_at`` descending (most recent first).","operationId":"list_mrms_files_route_v1_mrms_files_get","parameters":[{"name":"product","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single product (e.g. 'MergedReflectivityComposite')","title":"Product"},"description":"Filter to a single product (e.g. 'MergedReflectivityComposite')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single product level (e.g. '00.50')","title":"Level"},"description":"Filter to a single product level (e.g. '00.50')"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound on valid_at (ISO-8601 timestamp)","title":"Since"},"description":"Inclusive lower bound on valid_at (ISO-8601 timestamp)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound on valid_at (ISO-8601 timestamp)","title":"Until"},"description":"Exclusive upper bound on valid_at (ISO-8601 timestamp)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results to return (default 100)","default":100,"title":"Limit"},"description":"Max results to return (default 100)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsFileList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/mrms/grids":{"get":{"tags":["mrms"],"summary":"List materialised MRMS grids","description":"Returns the most-recent rows of the materialised-grid catalog populated by the ``aeroza-materialise-mrms`` worker. Each item carries the locator (``zarrUri``, ``shape``, ``dtype``, …) and the source file's product/level/valid_at. Same filters as ``/v1/mrms/files``.","operationId":"list_mrms_grids_route_v1_mrms_grids_get","parameters":[{"name":"product","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single product (e.g. 'MergedReflectivityComposite')","title":"Product"},"description":"Filter to a single product (e.g. 'MergedReflectivityComposite')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single product level (e.g. '00.50')","title":"Level"},"description":"Filter to a single product level (e.g. '00.50')"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound on valid_at (ISO-8601 timestamp)","title":"Since"},"description":"Inclusive lower bound on valid_at (ISO-8601 timestamp)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound on valid_at (ISO-8601 timestamp)","title":"Until"},"description":"Exclusive upper bound on valid_at (ISO-8601 timestamp)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results to return (default 100)","default":100,"title":"Limit"},"description":"Max results to return (default 100)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsGridList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/mrms/tiles/{z}/{x}/{y}.png":{"get":{"tags":["mrms"],"summary":"Raster tile of an MRMS grid (Web Mercator, XYZ)","description":"Returns a 256×256 raster tile suitable as a MapLibre / Leaflet raster source. By default renders the most recent ``MergedReflectivityComposite`` grid; pass ``file_key`` to pin a specific grid (used by the timeline scrubber). Tiles are sampled nearest-neighbor from the Zarr store, coloured with the standard NWS dBZ ramp, and 86%-opaque so the basemap shows through where there's no echo.\n\n**Content negotiation**: the URL ends in ``.png`` for MapLibre's tile-template compatibility, but the response is WebP when the request's ``Accept`` header includes ``image/webp`` (every modern browser does). WebP is ~30-40% smaller than PNG for the dBZ ramp's discrete colours and Pillow encodes it ~2x faster — both compound on top of the per-tile LRU.","operationId":"get_mrms_tile_route_v1_mrms_tiles__z___x___y__png_get","parameters":[{"name":"z","in":"path","required":true,"schema":{"type":"integer","maximum":10,"minimum":0,"description":"Zoom level (0–10).","title":"Z"},"description":"Zoom level (0–10)."},{"name":"x","in":"path","required":true,"schema":{"type":"integer","minimum":0,"description":"Tile column.","title":"X"},"description":"Tile column."},{"name":"y","in":"path","required":true,"schema":{"type":"integer","minimum":0,"description":"Tile row.","title":"Y"},"description":"Tile row."},{"name":"product","in":"query","required":false,"schema":{"type":"string","description":"MRMS product (e.g. 'MergedReflectivityComposite').","default":"MergedReflectivityComposite","title":"Product"},"description":"MRMS product (e.g. 'MergedReflectivityComposite')."},{"name":"level","in":"query","required":false,"schema":{"type":"string","description":"MRMS product level (e.g. '00.50').","default":"00.50","title":"Level"},"description":"MRMS product level (e.g. '00.50')."},{"name":"fileKey","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional pin to one specific source file_key. When omitted, the latest grid for the requested product/level is used.","title":"Filekey"},"description":"Optional pin to one specific source file_key. When omitted, the latest grid for the requested product/level is used."},{"name":"accept","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Browser-supplied content negotiation. Include ``image/webp`` to opt into the WebP encoding.","title":"Accept"},"description":"Browser-supplied content negotiation. Include ``image/webp`` to opt into the WebP encoding."}],"responses":{"200":{"description":"Tile PNG or WebP (per the request's Accept header).","content":{"image/png":{},"image/webp":{}}},"404":{"description":"No matching grid materialised yet."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/mrms/grids/sample":{"get":{"tags":["mrms"],"summary":"Sample one MRMS grid value at a (latitude, longitude) point","description":"Returns the nearest-cell value for ``(lat, lng)`` from a materialised MRMS grid. By default samples the *latest* grid for the given ``product``/``level``; pass ``at_time`` (ISO-8601 UTC) to sample the most-recent grid valid at-or-before that moment. Out-of-domain requests (no cell within ``tolerance_deg``) return 404 rather than a misleading nearest-edge value.","operationId":"sample_mrms_grid_route_v1_mrms_grids_sample_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90.0,"minimum":-90.0,"description":"Latitude in degrees (WGS84).","examples":[29.76],"title":"Lat"},"description":"Latitude in degrees (WGS84)."},{"name":"lng","in":"query","required":true,"schema":{"type":"number","maximum":180.0,"minimum":-180.0,"description":"Longitude in degrees (WGS84, -180..180).","examples":[-95.37],"title":"Lng"},"description":"Longitude in degrees (WGS84, -180..180)."},{"name":"product","in":"query","required":false,"schema":{"type":"string","description":"MRMS product (e.g. 'MergedReflectivityComposite').","default":"MergedReflectivityComposite","title":"Product"},"description":"MRMS product (e.g. 'MergedReflectivityComposite')."},{"name":"level","in":"query","required":false,"schema":{"type":"string","description":"MRMS product level (e.g. '00.50').","default":"00.50","title":"Level"},"description":"MRMS product level (e.g. '00.50')."},{"name":"at_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Sample the most recent grid with valid_at <= this moment. Defaults to the overall latest grid.","title":"At Time"},"description":"Sample the most recent grid with valid_at <= this moment. Defaults to the overall latest grid."},{"name":"tolerance_deg","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"description":"Reject the sample if no cell is within this many degrees (default 0.05; max 1.0).","default":0.05,"title":"Tolerance Deg"},"description":"Reject the sample if no cell is within this many degrees (default 0.05; max 1.0)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsGridSampleResponse"}}}},"404":{"description":"No materialised grid satisfies the request, or the request point is outside the grid (farther than ``tolerance_deg`` from any cell)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/mrms/grids/polygon":{"get":{"tags":["mrms"],"summary":"Reduce one MRMS grid over a polygon (max / mean / min / count_ge)","description":"Reduces ``variable`` over the cells of a materialised MRMS grid whose centres fall inside ``polygon``. Polygon vertices are flat comma-separated ``lng,lat,lng,lat,...`` (GeoJSON / OGC order), minimum three vertices, implicitly closed. By default samples the *latest* grid for the given ``product``/``level``; pass ``at_time`` (ISO-8601 UTC) to reduce the most-recent grid valid at-or-before that moment.\n\nReducers: ``max``, ``mean``, ``min``, ``count_ge`` (number of cells with value >= ``threshold``). ``count_ge`` requires ``threshold``; the others ignore it.","operationId":"reduce_mrms_grid_over_polygon_route_v1_mrms_grids_polygon_get","parameters":[{"name":"polygon","in":"query","required":true,"schema":{"type":"string","description":"Polygon vertices as ``lng,lat,lng,lat,...`` (GeoJSON / OGC order). Minimum 3 vertices; the ring is implicitly closed.","examples":["-95.7,29.5,-95.0,29.5,-95.0,30.0,-95.7,30.0"],"title":"Polygon"},"description":"Polygon vertices as ``lng,lat,lng,lat,...`` (GeoJSON / OGC order). Minimum 3 vertices; the ring is implicitly closed."},{"name":"reducer","in":"query","required":false,"schema":{"enum":["max","mean","min","count_ge"],"type":"string","description":"Reducer to apply. One of ['max', 'mean', 'min', 'count_ge'].","default":"max","title":"Reducer"},"description":"Reducer to apply. One of ['max', 'mean', 'min', 'count_ge']."},{"name":"threshold","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Required when ``reducer == 'count_ge'``: counts cells with value >= ``threshold``.","title":"Threshold"},"description":"Required when ``reducer == 'count_ge'``: counts cells with value >= ``threshold``."},{"name":"product","in":"query","required":false,"schema":{"type":"string","description":"MRMS product (e.g. 'MergedReflectivityComposite').","default":"MergedReflectivityComposite","title":"Product"},"description":"MRMS product (e.g. 'MergedReflectivityComposite')."},{"name":"level","in":"query","required":false,"schema":{"type":"string","description":"MRMS product level (e.g. '00.50').","default":"00.50","title":"Level"},"description":"MRMS product level (e.g. '00.50')."},{"name":"at_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Reduce the most recent grid with valid_at <= this moment. Defaults to the overall latest grid.","title":"At Time"},"description":"Reduce the most recent grid with valid_at <= this moment. Defaults to the overall latest grid."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsGridPolygonResponse"}}}},"404":{"description":"No grid satisfies the request, or the polygon does not overlap the grid (no cell centres inside it)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/mrms/grids/{file_key}":{"get":{"tags":["mrms"],"summary":"Get a single materialised MRMS grid by source S3 key","description":"Returns the locator + product/level/valid_at for one materialised grid identified by its source S3 ``file_key`` (the same key returned by ``/v1/mrms/files``). The ``:path`` converter accepts the slash-bearing CONUS-prefixed key as a single parameter.","operationId":"get_mrms_grid_route_v1_mrms_grids__file_key__get","parameters":[{"name":"file_key","in":"path","required":true,"schema":{"type":"string","minLength":1,"description":"S3 key of the source MRMS file (e.g. 'CONUS/.../MRMS_..._120000.grib2.gz')","title":"File Key"},"description":"S3 key of the source MRMS file (e.g. 'CONUS/.../MRMS_..._120000.grib2.gz')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsGridItem"}}}},"404":{"description":"No materialised grid for that file_key."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/nowcasts":{"get":{"tags":["nowcasts"],"summary":"List nowcasts (predicted grids)","description":"Returns the most-recent rows of the nowcast catalog populated by the ``aeroza-nowcast-mrms`` worker. Each row is one (algorithm, horizon) prediction derived from a source observation grid. Filters: ``product``, ``level``, ``algorithm`` (e.g. ``persistence``), ``horizon_minutes``, and a half-open ``[since, until)`` window on ``valid_at``.","operationId":"list_nowcasts_route_v1_nowcasts_get","parameters":[{"name":"product","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single product (e.g. 'MergedReflectivityComposite')","title":"Product"},"description":"Filter to a single product (e.g. 'MergedReflectivityComposite')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single product level (e.g. '00.50')","title":"Level"},"description":"Filter to a single product level (e.g. '00.50')"},{"name":"algorithm","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one algorithm tag (e.g. 'persistence', 'pysteps')","title":"Algorithm"},"description":"Filter to one algorithm tag (e.g. 'persistence', 'pysteps')"},{"name":"horizonMinutes","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter to one forecast horizon (e.g. 10, 30, 60).","title":"Horizonminutes"},"description":"Filter to one forecast horizon (e.g. 10, 30, 60)."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound on valid_at (ISO-8601 timestamp)","title":"Since"},"description":"Inclusive lower bound on valid_at (ISO-8601 timestamp)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound on valid_at (ISO-8601 timestamp)","title":"Until"},"description":"Exclusive upper bound on valid_at (ISO-8601 timestamp)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results to return (default 100)","default":100,"title":"Limit"},"description":"Max results to return (default 100)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NowcastList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/stats":{"get":{"tags":["meta"],"summary":"Live system stats: alerts + MRMS counts and freshness","description":"Compact 'what does the system know right now?' snapshot. Cheap aggregate counts (alerts active/total, MRMS files vs grids materialised) plus the latest ``valid_at`` and ``materialised_at`` timestamps so callers can confirm data is flowing without scanning the catalogs themselves.","operationId":"get_stats_route_v1_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}}}}},"/v1/calibration":{"get":{"tags":["calibration"],"summary":"Aggregate verification metrics, grouped by algorithm × horizon","description":"Returns sample-weighted MAE / bias / RMSE for every (algorithm, forecastHorizonMinutes) pair that has scored verifications inside the requested window, alongside categorical POD / FAR / CSI from the summed contingency table and probabilistic Brier / CRPS over any ensemble rows in the bucket. The public face of the §3.3 calibration moat — point a chart at this and you can watch a real algorithm pull ahead of the persistence baseline.\n\nProbabilistic fields (``brierMean``, ``crpsMean``, ``ensembleSize``) are ``null`` for buckets that contain no ensemble forecasts; ``brierSampleCount`` reports the cells behind the means so you can detect 'one tiny ensemble row'.\n\nWindow defaults to the last 24h. Pass ``windowHours`` to widen or narrow it; pass ``algorithm`` / ``product`` / ``level`` to scope the aggregation.","operationId":"get_calibration_route_v1_calibration_get","parameters":[{"name":"windowHours","in":"query","required":false,"schema":{"type":"integer","maximum":720,"minimum":1,"description":"Lookback window in hours (default 24).","default":24,"title":"Windowhours"},"description":"Lookback window in hours (default 24)."},{"name":"algorithm","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one algorithm tag (e.g. 'persistence')","title":"Algorithm"},"description":"Filter to one algorithm tag (e.g. 'persistence')"},{"name":"product","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one product (e.g. 'MergedReflectivityComposite')","title":"Product"},"description":"Filter to one product (e.g. 'MergedReflectivityComposite')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one product level (e.g. '00.50')","title":"Level"},"description":"Filter to one product level (e.g. '00.50')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalibrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calibration/series":{"get":{"tags":["calibration"],"summary":"Time-series calibration metrics, per (algorithm × horizon × bucket)","description":"Sparkline-shaped companion to ``/v1/calibration``: same sample-weighted means and probabilistic Brier/CRPS aggregates, but each (algorithm, forecastHorizonMinutes) row carries an ordered list of bucketed points so the front-end can chart how every metric moves over the window.\n\n``bucketSeconds`` controls bucket width (default 1 h). Series are sorted ``(algorithm, horizon, bucketStart)`` so the wire shape is render-ready.","operationId":"get_calibration_series_route_v1_calibration_series_get","parameters":[{"name":"windowHours","in":"query","required":false,"schema":{"type":"integer","maximum":720,"minimum":1,"description":"Lookback window in hours (default 24).","default":24,"title":"Windowhours"},"description":"Lookback window in hours (default 24)."},{"name":"bucketSeconds","in":"query","required":false,"schema":{"type":"integer","maximum":86400,"minimum":300,"description":"Bucket width in seconds (default 3600, min 300, max 86400).","default":3600,"title":"Bucketseconds"},"description":"Bucket width in seconds (default 3600, min 300, max 86400)."},{"name":"algorithm","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one algorithm tag (e.g. 'persistence')","title":"Algorithm"},"description":"Filter to one algorithm tag (e.g. 'persistence')"},{"name":"product","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one product (e.g. 'MergedReflectivityComposite')","title":"Product"},"description":"Filter to one product (e.g. 'MergedReflectivityComposite')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one product level (e.g. '00.50')","title":"Level"},"description":"Filter to one product level (e.g. '00.50')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalibrationSeriesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/metar":{"get":{"tags":["metar"],"summary":"List Metar Observations Route","description":"List recent METAR observations, newest first.","operationId":"list_metar_observations_route_v1_metar_get","parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one ICAO station id (e.g. KIAH). Case-insensitive.","title":"Station"},"description":"Filter to one ICAO station id (e.g. KIAH). Case-insensitive."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO-8601 lower bound (inclusive) on observationTime.","title":"Since"},"description":"ISO-8601 lower bound (inclusive) on observationTime."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"ISO-8601 upper bound (exclusive) on observationTime.","title":"Until"},"description":"ISO-8601 upper bound (exclusive) on observationTime."},{"name":"bbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"min_lng,min_lat,max_lng,max_lat — same convention as /v1/alerts.","title":"Bbox"},"description":"min_lng,min_lat,max_lng,max_lat — same convention as /v1/alerts."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum rows (default 100, max 500).","default":100,"title":"Limit"},"description":"Maximum rows (default 100, max 500)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetarObservationList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/metar/{station_id}/latest":{"get":{"tags":["metar"],"summary":"Get Latest Metar Route","description":"Most recent observation for one station.","operationId":"get_latest_metar_route_v1_metar__station_id__latest_get","parameters":[{"name":"station_id","in":"path","required":true,"schema":{"type":"string","minLength":3,"maxLength":8,"description":"ICAO 4-letter station id (e.g. KIAH). Case-insensitive.","title":"Station Id"},"description":"ICAO 4-letter station id (e.g. KIAH). Case-insensitive."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetarObservationItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks":{"post":{"tags":["webhooks"],"summary":"Create a webhook subscription","description":"Creates a subscription that the dispatcher worker will fan matching events out to. The ``secret`` field on the response is the HMAC signing key — it is shown **once** on creation and is omitted from every subsequent read; store it on your side.","operationId":"create_webhook_route_v1_webhooks_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["webhooks"],"summary":"List webhook subscriptions (newest first)","description":"Returns subscriptions ordered by ``created_at`` descending. The signing ``secret`` is omitted from every item — it is only ever returned on the create response.","operationId":"list_webhooks_route_v1_webhooks_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single status (active / paused / disabled).","title":"Status"},"description":"Filter to a single status (active / paused / disabled)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results to return (default 100).","default":100,"title":"Limit"},"description":"Max results to return (default 100)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{sub_id}":{"get":{"tags":["webhooks"],"summary":"Get a webhook subscription by id","operationId":"get_webhook_route_v1_webhooks__sub_id__get","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Subscription id (UUID).","title":"Sub Id"},"description":"Subscription id (UUID)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionRedacted"}}}},"404":{"description":"Subscription not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["webhooks"],"summary":"Update a webhook subscription","description":"Partial update. Every field is optional; absent fields are left untouched. ``events`` overwrites the full list — add/remove deltas are not supported on the wire.","operationId":"update_webhook_route_v1_webhooks__sub_id__patch","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Subscription id (UUID).","title":"Sub Id"},"description":"Subscription id (UUID)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionRedacted"}}}},"404":{"description":"Subscription not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["webhooks"],"summary":"Delete a webhook subscription","description":"Idempotent — second delete returns 404.","operationId":"delete_webhook_route_v1_webhooks__sub_id__delete","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Subscription id (UUID).","title":"Sub Id"},"description":"Subscription id (UUID)."}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"Subscription not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{sub_id}/deliveries":{"get":{"tags":["webhooks"],"summary":"Recent delivery attempts for a subscription","description":"Returns delivery rows ordered by ``created_at`` descending — one row per attempt the dispatcher made (initial + retries). Newest-first matches \"why did it just fail?\" debugging.\n\nOptional ``status`` filter narrows to a single outcome (``ok`` / ``failed`` / ``retrying``). The signed payload itself is omitted from the wire — operators that need it can read ``webhook_deliveries.payload`` directly.","operationId":"list_webhook_deliveries_route_v1_webhooks__sub_id__deliveries_get","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Subscription id (UUID).","title":"Sub Id"},"description":"Subscription id (UUID)."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single status (ok / failed / retrying).","title":"Status"},"description":"Filter to a single status (ok / failed / retrying)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Max attempts to return (default 50).","default":50,"title":"Limit"},"description":"Max attempts to return (default 50)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"404":{"description":"Subscription not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/alert-rules":{"post":{"tags":["webhooks"],"summary":"Create an alert rule","description":"Creates a rule bound to an existing webhook subscription. The dispatcher worker evaluates every active rule on each new MRMS grid; on a false→true predicate transition it POSTs the evaluation to the bound subscription's URL with the standard Aeroza HMAC headers.","operationId":"create_alert_rule_route_v1_alert_rules_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRuleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRule"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["webhooks"],"summary":"List alert rules (newest first)","operationId":"list_alert_rules_route_v1_alert_rules_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single status (active / paused / disabled).","title":"Status"},"description":"Filter to a single status (active / paused / disabled)."},{"name":"subscriptionId","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to rules bound to this subscription.","title":"Subscriptionid"},"description":"Filter to rules bound to this subscription."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results to return (default 100).","default":100,"title":"Limit"},"description":"Max results to return (default 100)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRuleList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/alert-rules/{rule_id}":{"get":{"tags":["webhooks"],"summary":"Get an alert rule by id","operationId":"get_alert_rule_route_v1_alert_rules__rule_id__get","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Rule id (UUID).","title":"Rule Id"},"description":"Rule id (UUID)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRule"}}}},"404":{"description":"Rule not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["webhooks"],"summary":"Update an alert rule","operationId":"update_alert_rule_route_v1_alert_rules__rule_id__patch","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Rule id (UUID).","title":"Rule Id"},"description":"Rule id (UUID)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRulePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRule"}}}},"404":{"description":"Rule not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["webhooks"],"summary":"Delete an alert rule","description":"Idempotent — second delete returns 404.","operationId":"delete_alert_rule_route_v1_alert_rules__rule_id__delete","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Rule id (UUID).","title":"Rule Id"},"description":"Rule id (UUID)."}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"Rule not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me":{"get":{"tags":["auth"],"summary":"Get Me","description":"Introspect the calling API key.\n\nThe bearer token already authenticated us, so the answer is\nderived from :class:`AuthenticatedKey` plus a single read for\n``last_used_at`` (which the auth dependency just bumped — but the\nin-memory snapshot only has the *previous* value).","operationId":"get_me_v1_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/seed-event":{"post":{"tags":["admin"],"summary":"Seed a curated historical event into the local archive","description":"Background-fires the ingest → materialise pipeline for the given ``[since, until]`` window. Returns immediately with a task snapshot; poll ``GET /v1/admin/seed-event/status`` for progress. Idempotent: a second POST for the same window returns the existing in-flight task.\n\nGated by the ``AEROZA_DEV_ADMIN_ENABLED`` env flag (default true). When the flag is false, the route 404s.","operationId":"post_seed_event_route_v1_admin_seed_event_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeedEventRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeedEventTaskSnapshot"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/admin/seed-event/status":{"get":{"tags":["admin"],"summary":"Read-only snapshot of a seed-event task","description":"Returns the current state of the seed for the given ``[since, until]`` window, or 404 if no task exists. The client polls this every few seconds while the button shows a progress spinner; once ``state`` flips to ``succeeded`` or ``failed`` the UI stops polling.","operationId":"get_seed_event_status_route_v1_admin_seed_event_status_get","parameters":[{"name":"since","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"Inclusive lower bound (ISO-8601, tz-aware).","title":"Since"},"description":"Inclusive lower bound (ISO-8601, tz-aware)."},{"name":"until","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"Exclusive upper bound (ISO-8601, tz-aware).","title":"Until"},"description":"Exclusive upper bound (ISO-8601, tz-aware)."},{"name":"product","in":"query","required":false,"schema":{"type":"string","description":"MRMS product.","default":"MergedReflectivityComposite","title":"Product"},"description":"MRMS product."},{"name":"level","in":"query","required":false,"schema":{"type":"string","description":"MRMS product level.","default":"00.50","title":"Level"},"description":"MRMS product level."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeedEventTaskSnapshot"}}}},"404":{"description":"No task for the requested window."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AlertDetailFeature":{"properties":{"type":{"type":"string","const":"Feature","title":"Type","default":"Feature"},"geometry":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Geometry"},"properties":{"$ref":"#/components/schemas/AlertDetailProperties"}},"type":"object","required":["properties"],"title":"AlertDetailFeature"},"AlertDetailProperties":{"properties":{"id":{"type":"string","title":"Id"},"event":{"type":"string","title":"Event"},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline"},"severity":{"type":"string","title":"Severity"},"urgency":{"type":"string","title":"Urgency"},"certainty":{"type":"string","title":"Certainty"},"senderName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sendername"},"areaDesc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Areadesc"},"effective":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Effective"},"onset":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Onset"},"expires":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires"},"ends":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ends"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"instruction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instruction"}},"type":"object","required":["id","event","severity","urgency","certainty"],"title":"AlertDetailProperties","description":"Per-alert metadata for the detail endpoint, including long-form prose."},"AlertFeature":{"properties":{"type":{"type":"string","const":"Feature","title":"Type","default":"Feature"},"geometry":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Geometry"},"properties":{"$ref":"#/components/schemas/AlertProperties"}},"type":"object","required":["properties"],"title":"AlertFeature"},"AlertFeatureCollection":{"properties":{"type":{"type":"string","const":"FeatureCollection","title":"Type","default":"FeatureCollection"},"features":{"items":{"$ref":"#/components/schemas/AlertFeature"},"type":"array","title":"Features"}},"type":"object","required":["features"],"title":"AlertFeatureCollection"},"AlertProperties":{"properties":{"id":{"type":"string","title":"Id"},"event":{"type":"string","title":"Event"},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline"},"severity":{"type":"string","title":"Severity"},"urgency":{"type":"string","title":"Urgency"},"certainty":{"type":"string","title":"Certainty"},"senderName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sendername"},"areaDesc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Areadesc"},"effective":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Effective"},"onset":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Onset"},"expires":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires"},"ends":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ends"}},"type":"object","required":["id","event","severity","urgency","certainty"],"title":"AlertProperties","description":"Per-alert metadata for the list endpoint.\n\nExcludes ``description`` and ``instruction`` to keep list payloads small;\ncallers wanting the full record should use ``GET /v1/alerts/{id}``."},"AlertRule":{"properties":{"type":{"type":"string","const":"AlertRule","title":"Type","default":"AlertRule"},"id":{"type":"string","format":"uuid","title":"Id"},"subscriptionId":{"type":"string","format":"uuid","title":"Subscriptionid"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"config":{"oneOf":[{"$ref":"#/components/schemas/PointRuleConfig"},{"$ref":"#/components/schemas/PolygonRuleConfig"}],"title":"Config","discriminator":{"propertyName":"type","mapping":{"point":"#/components/schemas/PointRuleConfig","polygon":"#/components/schemas/PolygonRuleConfig"}}},"status":{"type":"string","enum":["active","paused","disabled"],"title":"Status"},"currentlyFiring":{"type":"boolean","title":"Currentlyfiring"},"lastValue":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lastvalue"},"lastEvaluatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastevaluatedat"},"lastFiredAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastfiredat"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"}},"type":"object","required":["id","subscriptionId","name","description","config","status","currentlyFiring","lastValue","lastEvaluatedAt","lastFiredAt","createdAt","updatedAt"],"title":"AlertRule","description":"Response shape for every read endpoint.\n\nMirrors the row 1:1 — there's no secret-redaction analog here,\nso a single response model covers create / list / get / patch."},"AlertRuleCreate":{"properties":{"subscriptionId":{"type":"string","format":"uuid","title":"Subscriptionid"},"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"},"config":{"oneOf":[{"$ref":"#/components/schemas/PointRuleConfig"},{"$ref":"#/components/schemas/PolygonRuleConfig"}],"title":"Config","discriminator":{"propertyName":"type","mapping":{"point":"#/components/schemas/PointRuleConfig","polygon":"#/components/schemas/PolygonRuleConfig"}}}},"type":"object","required":["subscriptionId","name","config"],"title":"AlertRuleCreate","description":"Request body for ``POST /v1/alert-rules``."},"AlertRuleList":{"properties":{"type":{"type":"string","const":"AlertRuleList","title":"Type","default":"AlertRuleList"},"items":{"items":{"$ref":"#/components/schemas/AlertRule"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"AlertRuleList","description":"Envelope for ``GET /v1/alert-rules``."},"AlertRulePatch":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"},"config":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/PointRuleConfig"},{"$ref":"#/components/schemas/PolygonRuleConfig"}],"discriminator":{"propertyName":"type","mapping":{"point":"#/components/schemas/PointRuleConfig","polygon":"#/components/schemas/PolygonRuleConfig"}}},{"type":"null"}],"title":"Config"},"status":{"anyOf":[{"type":"string","enum":["active","paused","disabled"]},{"type":"null"}],"title":"Status"}},"type":"object","title":"AlertRulePatch","description":"Request body for ``PATCH /v1/alert-rules/{id}``.\n\nEvery field is optional. ``config`` is replaced wholesale —\nsub-field deltas aren't supported on the wire (small enough to\nread/edit/write)."},"AlertsStats":{"properties":{"total":{"type":"integer","title":"Total"},"active":{"type":"integer","title":"Active"},"latestExpires":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latestexpires"}},"type":"object","required":["total","active"],"title":"AlertsStats","description":"Per-domain rollup for NWS alerts."},"CalibrationResponse":{"properties":{"type":{"type":"string","const":"Calibration","title":"Type","default":"Calibration"},"generatedAt":{"type":"string","format":"date-time","title":"Generatedat"},"windowHours":{"type":"integer","title":"Windowhours"},"items":{"items":{"$ref":"#/components/schemas/CalibrationRow"},"type":"array","title":"Items"},"reliability":{"items":{"$ref":"#/components/schemas/ReliabilityRow"},"type":"array","title":"Reliability","default":[]}},"type":"object","required":["generatedAt","windowHours","items"],"title":"CalibrationResponse","description":"Wire shape for ``GET /v1/calibration``."},"CalibrationRow":{"properties":{"algorithm":{"type":"string","title":"Algorithm"},"forecastHorizonMinutes":{"type":"integer","title":"Forecasthorizonminutes"},"verificationCount":{"type":"integer","title":"Verificationcount"},"sampleCount":{"type":"integer","title":"Samplecount"},"maeMean":{"type":"number","title":"Maemean"},"biasMean":{"type":"number","title":"Biasmean"},"rmseMean":{"type":"number","title":"Rmsemean"},"thresholdDbz":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Thresholddbz"},"pod":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pod"},"far":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Far"},"csi":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Csi"},"ensembleSize":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ensemblesize"},"brierSampleCount":{"type":"integer","title":"Briersamplecount","default":0},"brierMean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Briermean"},"crpsMean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Crpsmean"}},"type":"object","required":["algorithm","forecastHorizonMinutes","verificationCount","sampleCount","maeMean","biasMean","rmseMean"],"title":"CalibrationRow","description":"One row of the calibration aggregate.\n\nPer (algorithm × forecast horizon) over the requested window.\nSample-weighted means: a verification with N=1M cells contributes\nN times to ``maeMean`` / ``biasMean`` / ``rmseMean``.\n\nCategorical fields (``pod``, ``far``, ``csi``) are computed at\nserialization time from the summed contingency table — averaging\nPOD/FAR/CSI across rows would be wrong (averaging ratios is not\nthe same as the ratio of averages). They're nullable: when no\ncontributing row had categorical metrics or the threshold was\nmixed, we surface ``null`` rather than a misleading 0.\n\nProbabilistic fields (``brierMean``, ``crpsMean``,\n``ensembleSize``) are populated only when at least one ensemble\nnowcast contributed to the bucket. ``brierMean`` and ``crpsMean``\nare sample-weighted across only the ensemble rows, so a bucket\nthat mixes deterministic and ensemble forecasts surfaces an\napples-to-apples Brier/CRPS for the ensemble subset (with\n``brierSampleCount`` reporting the cell count behind it)."},"CalibrationSeriesItem":{"properties":{"algorithm":{"type":"string","title":"Algorithm"},"forecastHorizonMinutes":{"type":"integer","title":"Forecasthorizonminutes"},"points":{"items":{"$ref":"#/components/schemas/CalibrationSeriesItemPoint"},"type":"array","title":"Points"}},"type":"object","required":["algorithm","forecastHorizonMinutes","points"],"title":"CalibrationSeriesItem","description":"All buckets for one (algorithm, horizon) — the per-row sparkline data."},"CalibrationSeriesItemPoint":{"properties":{"bucketStart":{"type":"string","format":"date-time","title":"Bucketstart"},"verificationCount":{"type":"integer","title":"Verificationcount"},"sampleCount":{"type":"integer","title":"Samplecount"},"maeMean":{"type":"number","title":"Maemean"},"biasMean":{"type":"number","title":"Biasmean"},"rmseMean":{"type":"number","title":"Rmsemean"},"thresholdDbz":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Thresholddbz"},"pod":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pod"},"far":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Far"},"csi":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Csi"},"ensembleSize":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ensemblesize"},"brierSampleCount":{"type":"integer","title":"Briersamplecount","default":0},"brierMean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Briermean"},"crpsMean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Crpsmean"}},"type":"object","required":["bucketStart","verificationCount","sampleCount","maeMean","biasMean","rmseMean"],"title":"CalibrationSeriesItemPoint","description":"One time-bucket on a calibration sparkline."},"CalibrationSeriesResponse":{"properties":{"type":{"type":"string","const":"CalibrationSeries","title":"Type","default":"CalibrationSeries"},"generatedAt":{"type":"string","format":"date-time","title":"Generatedat"},"windowHours":{"type":"integer","title":"Windowhours"},"bucketSeconds":{"type":"integer","title":"Bucketseconds"},"items":{"items":{"$ref":"#/components/schemas/CalibrationSeriesItem"},"type":"array","title":"Items"}},"type":"object","required":["generatedAt","windowHours","bucketSeconds","items"],"title":"CalibrationSeriesResponse","description":"Wire shape for ``GET /v1/calibration/series``.\n\n``bucketSeconds`` is the width of each bucket (e.g. 3600 for hourly).\n``items`` is one element per (algorithm × horizon); each carries an\nordered list of points (oldest → newest)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MeResponse":{"properties":{"type":{"type":"string","const":"Me","title":"Type","default":"Me"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"owner":{"type":"string","title":"Owner"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"rateLimitClass":{"type":"string","title":"Ratelimitclass"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastusedat"}},"type":"object","required":["name","prefix","owner","scopes","rateLimitClass"],"title":"MeResponse","description":"The calling key's metadata, redacted to what the caller already knows.\n\nNotably absent: ``key_hash`` (server-only), the full token (only\never shown at mint time), and ``created_at`` /\n``updated_at`` (administrative metadata the caller hasn't asked\nfor). Add fields here as concrete needs surface."},"MetarObservationItem":{"properties":{"type":{"type":"string","const":"MetarObservation","title":"Type","default":"MetarObservation"},"stationId":{"type":"string","title":"Stationid"},"observationTime":{"type":"string","format":"date-time","title":"Observationtime"},"latitude":{"type":"number","title":"Latitude"},"longitude":{"type":"number","title":"Longitude"},"rawText":{"type":"string","title":"Rawtext"},"tempC":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tempc"},"dewpointC":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dewpointc"},"windSpeedKt":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Windspeedkt"},"windDirectionDeg":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Winddirectiondeg"},"windGustKt":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Windgustkt"},"visibilitySm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Visibilitysm"},"altimeterHpa":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Altimeterhpa"},"flightCategory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flightcategory"}},"type":"object","required":["stationId","observationTime","latitude","longitude","rawText"],"title":"MetarObservationItem","description":"One row of ``GET /v1/metar`` / ``GET /v1/metar/{station}/latest``."},"MetarObservationList":{"properties":{"type":{"type":"string","const":"MetarObservationList","title":"Type","default":"MetarObservationList"},"items":{"items":{"$ref":"#/components/schemas/MetarObservationItem"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"MetarObservationList","description":"Wire shape for ``GET /v1/metar``."},"MrmsFileItem":{"properties":{"key":{"type":"string","title":"Key"},"product":{"type":"string","title":"Product"},"level":{"type":"string","title":"Level"},"validAt":{"type":"string","format":"date-time","title":"Validat"},"sizeBytes":{"type":"integer","title":"Sizebytes"},"etag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Etag"}},"type":"object","required":["key","product","level","validAt","sizeBytes"],"title":"MrmsFileItem","description":"One file in the catalog, formatted for the wire."},"MrmsFileList":{"properties":{"type":{"type":"string","const":"MrmsFileList","title":"Type","default":"MrmsFileList"},"items":{"items":{"$ref":"#/components/schemas/MrmsFileItem"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"MrmsFileList","description":"Envelope returned by the list route."},"MrmsGridItem":{"properties":{"fileKey":{"type":"string","title":"Filekey"},"product":{"type":"string","title":"Product"},"level":{"type":"string","title":"Level"},"validAt":{"type":"string","format":"date-time","title":"Validat"},"zarrUri":{"type":"string","title":"Zarruri"},"variable":{"type":"string","title":"Variable"},"dims":{"items":{"type":"string"},"type":"array","title":"Dims"},"shape":{"items":{"type":"integer"},"type":"array","title":"Shape"},"dtype":{"type":"string","title":"Dtype"},"nbytes":{"type":"integer","title":"Nbytes"},"materialisedAt":{"type":"string","format":"date-time","title":"Materialisedat"}},"type":"object","required":["fileKey","product","level","validAt","zarrUri","variable","dims","shape","dtype","nbytes","materialisedAt"],"title":"MrmsGridItem","description":"One materialised grid, formatted for the wire."},"MrmsGridList":{"properties":{"type":{"type":"string","const":"MrmsGridList","title":"Type","default":"MrmsGridList"},"items":{"items":{"$ref":"#/components/schemas/MrmsGridItem"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"MrmsGridList","description":"Envelope returned by the list route."},"MrmsGridPolygonResponse":{"properties":{"type":{"type":"string","const":"MrmsGridPolygonSample","title":"Type","default":"MrmsGridPolygonSample"},"fileKey":{"type":"string","title":"Filekey"},"product":{"type":"string","title":"Product"},"level":{"type":"string","title":"Level"},"validAt":{"type":"string","format":"date-time","title":"Validat"},"variable":{"type":"string","title":"Variable"},"reducer":{"type":"string","enum":["max","mean","min","count_ge"],"title":"Reducer"},"threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold"},"value":{"type":"number","title":"Value"},"cellCount":{"type":"integer","title":"Cellcount"},"vertexCount":{"type":"integer","title":"Vertexcount"},"bboxMinLatitude":{"type":"number","title":"Bboxminlatitude"},"bboxMinLongitude":{"type":"number","title":"Bboxminlongitude"},"bboxMaxLatitude":{"type":"number","title":"Bboxmaxlatitude"},"bboxMaxLongitude":{"type":"number","title":"Bboxmaxlongitude"}},"type":"object","required":["fileKey","product","level","validAt","variable","reducer","threshold","value","cellCount","vertexCount","bboxMinLatitude","bboxMinLongitude","bboxMaxLatitude","bboxMaxLongitude"],"title":"MrmsGridPolygonResponse","description":"Wire shape returned by ``GET /v1/mrms/grids/polygon``.\n\nCarries the reducer's value, the count of cells that landed inside\nthe polygon, and the grid's bounding box of those cells. For\n``count_ge``, ``threshold`` echoes the request and ``value`` is the\ncount expressed as a float (so the JSON shape matches the other\nreducers and downstream parsers don't have to branch on type)."},"MrmsGridSampleResponse":{"properties":{"type":{"type":"string","const":"MrmsGridSample","title":"Type","default":"MrmsGridSample"},"fileKey":{"type":"string","title":"Filekey"},"product":{"type":"string","title":"Product"},"level":{"type":"string","title":"Level"},"validAt":{"type":"string","format":"date-time","title":"Validat"},"variable":{"type":"string","title":"Variable"},"value":{"type":"number","title":"Value"},"requestedLatitude":{"type":"number","title":"Requestedlatitude"},"requestedLongitude":{"type":"number","title":"Requestedlongitude"},"matchedLatitude":{"type":"number","title":"Matchedlatitude"},"matchedLongitude":{"type":"number","title":"Matchedlongitude"},"toleranceDeg":{"type":"number","title":"Tolerancedeg"}},"type":"object","required":["fileKey","product","level","validAt","variable","value","requestedLatitude","requestedLongitude","matchedLatitude","matchedLongitude","toleranceDeg"],"title":"MrmsGridSampleResponse","description":"Wire shape returned by ``GET /v1/mrms/grids/sample``.\n\nCarries both the value and the *actual* cell coordinates the value\ncame from — callers that snap user input to a known cell (for\ndeduplication, caching, downstream joins) need the matched coords,\nnot just the requested ones. ``validAt`` and ``fileKey`` identify\nthe source grid so a follow-up call to ``/v1/mrms/grids/{file_key}``\ncan fetch the rest of the locator."},"MrmsStats":{"properties":{"files":{"type":"integer","title":"Files"},"gridsMaterialised":{"type":"integer","title":"Gridsmaterialised"},"filesPending":{"type":"integer","title":"Filespending"},"latestValidAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latestvalidat"},"latestGridMaterialisedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latestgridmaterialisedat"}},"type":"object","required":["files","gridsMaterialised","filesPending"],"title":"MrmsStats","description":"Per-domain rollup for the MRMS catalog + materialised grids."},"NowcastItem":{"properties":{"id":{"type":"string","title":"Id"},"sourceFileKey":{"type":"string","title":"Sourcefilekey"},"product":{"type":"string","title":"Product"},"level":{"type":"string","title":"Level"},"algorithm":{"type":"string","title":"Algorithm"},"forecastHorizonMinutes":{"type":"integer","title":"Forecasthorizonminutes"},"validAt":{"type":"string","format":"date-time","title":"Validat"},"zarrUri":{"type":"string","title":"Zarruri"},"variable":{"type":"string","title":"Variable"},"dims":{"items":{"type":"string"},"type":"array","title":"Dims"},"shape":{"items":{"type":"integer"},"type":"array","title":"Shape"},"dtype":{"type":"string","title":"Dtype"},"nbytes":{"type":"integer","title":"Nbytes"},"generatedAt":{"type":"string","format":"date-time","title":"Generatedat"}},"type":"object","required":["id","sourceFileKey","product","level","algorithm","forecastHorizonMinutes","validAt","zarrUri","variable","dims","shape","dtype","nbytes","generatedAt"],"title":"NowcastItem","description":"One nowcast row, formatted for the wire."},"NowcastList":{"properties":{"type":{"type":"string","const":"NowcastList","title":"Type","default":"NowcastList"},"items":{"items":{"$ref":"#/components/schemas/NowcastItem"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"NowcastList","description":"Envelope returned by ``GET /v1/nowcasts``."},"PointRuleConfig":{"properties":{"product":{"type":"string","title":"Product","default":"MergedReflectivityComposite"},"level":{"type":"string","title":"Level","default":"00.50"},"predicate":{"$ref":"#/components/schemas/Predicate"},"type":{"type":"string","const":"point","title":"Type","default":"point"},"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lng":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lng"}},"type":"object","required":["predicate","lat","lng"],"title":"PointRuleConfig","description":"Predicate over the value sampled at a (lat, lng)."},"PolygonRuleConfig":{"properties":{"product":{"type":"string","title":"Product","default":"MergedReflectivityComposite"},"level":{"type":"string","title":"Level","default":"00.50"},"predicate":{"$ref":"#/components/schemas/Predicate"},"type":{"type":"string","const":"polygon","title":"Type","default":"polygon"},"polygon":{"type":"string","title":"Polygon"},"reducer":{"type":"string","enum":["max","mean","min","count_ge"],"title":"Reducer","default":"max"},"countThreshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Countthreshold"}},"type":"object","required":["predicate","polygon"],"title":"PolygonRuleConfig","description":"Predicate over a polygon reducer.\n\n``polygon`` matches the format the ``/v1/mrms/grids/polygon`` route\naccepts: flat ``\"lng,lat,lng,lat,…\"`` with ≥3 vertices, ring\nimplicitly closed. Validation here pre-rejects obviously malformed\nstrings; the route's :func:`parse_polygon` is the canonical parser\nand runs again at evaluation time."},"Predicate":{"properties":{"op":{"type":"string","enum":[">",">=","<","<=","==","!="],"title":"Op"},"threshold":{"type":"number","title":"Threshold"}},"type":"object","required":["op","threshold"],"title":"Predicate","description":"``value op threshold``. ``op`` is a comparator; ``threshold`` is float.\n\nEquality (``==``/``!=``) is legal but rarely useful for floating-point\ngrid samples — included for completeness so the dispatcher can\ntreat the operator set as a closed enum."},"ReliabilityBinResponse":{"properties":{"lower":{"type":"number","title":"Lower"},"count":{"type":"integer","title":"Count"},"observed":{"type":"integer","title":"Observed"},"meanProb":{"type":"number","title":"Meanprob"},"observedFrequency":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Observedfrequency"}},"type":"object","required":["lower","count","observed","meanProb","observedFrequency"],"title":"ReliabilityBinResponse","description":"One bin of a reliability diagram on the wire.\n\nMirrors :class:`aeroza.verify.metrics.ReliabilityBin` but uses\nPydantic so FastAPI can serialise it. ``lower`` is the inclusive\nlower edge of the forecast-probability bucket; ``observedFrequency``\nis the chart's y-coordinate (None when the bin is empty so the\nUI can skip the dot rather than plot at the origin)."},"ReliabilityRow":{"properties":{"algorithm":{"type":"string","title":"Algorithm"},"forecastHorizonMinutes":{"type":"integer","title":"Forecasthorizonminutes"},"bins":{"items":{"$ref":"#/components/schemas/ReliabilityBinResponse"},"type":"array","title":"Bins"}},"type":"object","required":["algorithm","forecastHorizonMinutes","bins"],"title":"ReliabilityRow","description":"Per-(algorithm × horizon) reliability data, attached to the\ncalibration response so the UI can render the diagram alongside\nthe matrix without a second fetch."},"SeedEventRequest":{"properties":{"since":{"type":"string","format":"date-time","title":"Since","description":"Inclusive lower bound of the historical window (ISO-8601, tz-aware).","examples":["2021-02-14T22:00:00Z"]},"until":{"type":"string","format":"date-time","title":"Until","description":"Exclusive upper bound of the historical window (ISO-8601, tz-aware).","examples":["2021-02-15T16:00:00Z"]},"product":{"type":"string","title":"Product","description":"MRMS product (defaults to MergedReflectivityComposite).","default":"MergedReflectivityComposite"},"level":{"type":"string","title":"Level","description":"MRMS product level (defaults to '00.50').","default":"00.50"}},"type":"object","required":["since","until"],"title":"SeedEventRequest","description":"Body for ``POST /v1/admin/seed-event``."},"SeedEventTaskSnapshot":{"properties":{"type":{"type":"string","const":"AdminSeedEventTask","title":"Type","default":"AdminSeedEventTask"},"since":{"type":"string","format":"date-time","title":"Since"},"until":{"type":"string","format":"date-time","title":"Until"},"product":{"type":"string","title":"Product"},"level":{"type":"string","title":"Level"},"startedAt":{"type":"string","format":"date-time","title":"Startedat"},"finishedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finishedat"},"cfgribAvailable":{"type":"boolean","title":"Cfgribavailable"},"filesInserted":{"type":"integer","title":"Filesinserted"},"filesUpdated":{"type":"integer","title":"Filesupdated"},"gridsMaterialised":{"type":"integer","title":"Gridsmaterialised"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"state":{"type":"string","enum":["running","succeeded","failed"],"title":"State"}},"type":"object","required":["since","until","product","level","startedAt","cfgribAvailable","filesInserted","filesUpdated","gridsMaterialised","state"],"title":"SeedEventTaskSnapshot","description":"Wire shape echoed by both endpoints.\n\nMirrors :class:`aeroza.admin.seed_event.SeedTask` — every field\nthat's safe to expose, plus a derived ``state`` so callers don't\nhave to compute it from ``finished_at`` / ``error``."},"Severity":{"type":"string","enum":["Extreme","Severe","Moderate","Minor","Unknown"],"title":"Severity"},"Stats":{"properties":{"type":{"type":"string","const":"Stats","title":"Type","default":"Stats"},"generatedAt":{"type":"string","format":"date-time","title":"Generatedat"},"alerts":{"$ref":"#/components/schemas/AlertsStats"},"mrms":{"$ref":"#/components/schemas/MrmsStats"}},"type":"object","required":["generatedAt","alerts","mrms"],"title":"Stats","description":"Top-level envelope returned by ``GET /v1/stats``."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WebhookDelivery":{"properties":{"type":{"type":"string","const":"WebhookDelivery","title":"Type","default":"WebhookDelivery"},"id":{"type":"string","format":"uuid","title":"Id"},"subscriptionId":{"type":"string","format":"uuid","title":"Subscriptionid"},"ruleId":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Ruleid"},"eventType":{"type":"string","title":"Eventtype"},"status":{"type":"string","enum":["ok","failed","retrying"],"title":"Status"},"attempt":{"type":"integer","title":"Attempt"},"responseStatus":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Responsestatus"},"responseBodyPreview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Responsebodypreview"},"errorReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Errorreason"},"durationMs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Durationms"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"}},"type":"object","required":["id","subscriptionId","eventType","status","attempt","createdAt"],"title":"WebhookDelivery","description":"One row of the audit trail.\n\nMirrors :class:`WebhookDeliveryRow` minus ``payload`` (see module\ndocstring). ``responseBodyPreview`` is already a server-side\ntruncated copy (see :data:`RESPONSE_BODY_PREVIEW_BYTES`), so it's\nsafe to surface as-is."},"WebhookDeliveryList":{"properties":{"type":{"type":"string","const":"WebhookDeliveryList","title":"Type","default":"WebhookDeliveryList"},"items":{"items":{"$ref":"#/components/schemas/WebhookDelivery"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"WebhookDeliveryList","description":"Envelope for ``GET /v1/webhooks/{id}/deliveries``."},"WebhookSubscription":{"properties":{"type":{"type":"string","const":"WebhookSubscription","title":"Type","default":"WebhookSubscription"},"id":{"type":"string","format":"uuid","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","enum":["active","paused","disabled"],"title":"Status"},"secret":{"type":"string","title":"Secret"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"}},"type":"object","required":["id","url","events","description","status","secret","createdAt","updatedAt"],"title":"WebhookSubscription","description":"Full response shape, including the signing secret.\n\nReturned only on the create response. Subsequent reads use\n:class:`WebhookSubscriptionRedacted` so the secret isn't exposed\nafter creation."},"WebhookSubscriptionCreate":{"properties":{"url":{"type":"string","maxLength":2048,"title":"Url","description":"Absolute https:// (or http:// in dev) URL."},"events":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Events","description":"List of event types to subscribe to. Each must be one of ['aeroza.alerts.nws.new', 'aeroza.mrms.files.new', 'aeroza.mrms.grids.new', 'aeroza.nowcast.grids.new']."},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description","description":"Human-readable label, surfaced in the dashboard / logs."}},"type":"object","required":["url","events"],"title":"WebhookSubscriptionCreate","description":"Request body for ``POST /v1/webhooks``."},"WebhookSubscriptionList":{"properties":{"type":{"type":"string","const":"WebhookSubscriptionList","title":"Type","default":"WebhookSubscriptionList"},"items":{"items":{"$ref":"#/components/schemas/WebhookSubscriptionRedacted"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"WebhookSubscriptionList","description":"Envelope for ``GET /v1/webhooks``."},"WebhookSubscriptionPatch":{"properties":{"url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Url"},"events":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Events"},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"},"status":{"anyOf":[{"type":"string","enum":["active","paused","disabled"]},{"type":"null"}],"title":"Status"}},"type":"object","title":"WebhookSubscriptionPatch","description":"Request body for ``PATCH /v1/webhooks/{id}``.\n\nEvery field is optional; absent fields are left untouched. Setting\n``events`` to a list overwrites; the API does not support add/remove\ndeltas (the round-trip is small enough that the client can read,\nedit, and write)."},"WebhookSubscriptionRedacted":{"properties":{"type":{"type":"string","const":"WebhookSubscriptionRedacted","title":"Type","default":"WebhookSubscriptionRedacted"},"id":{"type":"string","format":"uuid","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","enum":["active","paused","disabled"],"title":"Status"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"}},"type":"object","required":["id","url","events","description","status","createdAt","updatedAt"],"title":"WebhookSubscriptionRedacted","description":"Response shape that omits ``secret``.\n\nUsed by every read endpoint after creation. The secret is shown\nonce at creation time (à la Stripe webhook signing keys); operators\nthat need it again rotate it via a future ``POST\n/v1/webhooks/{id}/rotate-secret`` route (out of scope for this PR)."}}}}