{"openapi":"3.1.0","info":{"title":"StormTrust Data Read API","version":"1.0.1","description":"Versioned read access to StormTrust place search, storm events, source-backed weather revisions, and map-ready evidence. Ingestion controls and storage layouts are not part of this contract. Schema clarification release: publication ordering, nullable product status, and WebSocket messages. Runtime response shapes are unchanged."},"servers":[{"url":"https://stormtrust.net","description":"Public StormTrust website gateway"}],"tags":[{"name":"Places"},{"name":"Storm events"},{"name":"Data service"}],"paths":{"/v1/places/search":{"get":{"operationId":"searchPlaces","summary":"Search U.S. places","tags":["Places"],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":160}}],"responses":{"200":{"description":"Matching places","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceSearchResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/v1/storm-events":{"get":{"operationId":"listStormEvents","summary":"List storm events","tags":["Storm events"],"responses":{"200":{"description":"Available operational and benchmark events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StormEventList"}}}}},"description":"Returns at most 100 operational-feed or historical-event records ordered by endedAt or startedAt descending. These are feed/benchmark identities, not detected physical storms. No pagination or geographic filtering is implemented."}},"/v1/storm-events/{eventId}/revisions":{"get":{"operationId":"listStormEventRevisions","summary":"List map-ready event revisions","tags":["Storm events"],"parameters":[{"$ref":"#/components/parameters/EventId"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":2,"maximum":360,"default":24}}],"responses":{"200":{"description":"Latest bounded set of ready revisions, returned in ascending publication sequence (not observation-time order).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsHistory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}},"description":"Selects the most recently published ready revisions up to limit, then returns that subset in ascending sequence. Late corrections can have an older observedAt and a newer sequence. No pagination or complete-window guarantee is provided."}},"/v1/storm-events/{eventId}/manifest":{"get":{"operationId":"getLatestStormEventManifest","summary":"Get the latest complete event manifest","tags":["Storm events"],"parameters":[{"$ref":"#/components/parameters/EventId"}],"responses":{"200":{"description":"Latest complete map manifest","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsManifest"}}}},"304":{"description":"Manifest has not changed"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Stored manifest fails the runtime contract","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/storm-events/{eventId}/revisions/{revisionId}/manifest":{"get":{"operationId":"getStormEventRevisionManifest","summary":"Get an immutable revision manifest","tags":["Storm events"],"parameters":[{"$ref":"#/components/parameters/EventId"},{"$ref":"#/components/parameters/RevisionId"}],"responses":{"200":{"description":"Immutable map manifest for one revision","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MrmsManifest"}}}},"304":{"description":"Manifest has not changed"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Stored manifest fails the runtime contract","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/storm-events/{eventId}/frames/{productId}/{frameId}.png":{"get":{"operationId":"getStormEventFrame","summary":"Get an immutable map-ready weather frame","tags":["Storm events"],"parameters":[{"$ref":"#/components/parameters/EventId"},{"name":"productId","in":"path","required":true,"schema":{"type":"string","enum":["mrms-mesh","mrms-reflectivity"]}},{"name":"frameId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9-]+$"}}],"responses":{"200":{"description":"Transparent weather raster","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/storm-events/{eventId}/stream":{"get":{"operationId":"streamStormEventRevisions","summary":"Open a WebSocket revision stream","description":"Upgrade to WebSocket. JSON messages are stream.ready followed by storm.revision.ready notices. stream.ready may include the last retained notice. Notices are not a complete event log: the room suppresses sequences no newer than its last notice. Reconcile from the read API on reconnect. A client text ping receives a plain text pong; pong is not JSON. x-websocket-messages is a StormTrust documentation extension, not standard OpenAPI WebSocket transport modeling.","tags":["Storm events"],"parameters":[{"$ref":"#/components/parameters/EventId"}],"responses":{"101":{"description":"WebSocket protocol switched"},"400":{"description":"Invalid event identity","content":{"text/plain":{"schema":{"type":"string"}}}},"426":{"description":"A WebSocket upgrade is required"}},"x-websocket-messages":{"application/json":{"schema":{"$ref":"#/components/schemas/StormStreamMessage"}},"text/plain":{"schema":{"type":"string","const":"pong"}}}}},"/v1/data/status":{"get":{"operationId":"getDataServiceStatus","summary":"Get latest source-product status","tags":["Data service"],"responses":{"200":{"description":"Latest ingestion status by product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataServiceStatus"}}}}},"description":"Reports the latest discovered asset per product across operational and benchmark events. An empty products array means no source assets are recorded; it does not mean no weather. This endpoint does not evaluate lag, window completeness, or live-feed-only health."}}},"components":{"parameters":{"EventId":{"name":"eventId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"}},"RevisionId":{"name":"revisionId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9TZ-]+$"}}},"responses":{"BadRequest":{"description":"The request is invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"The requested data is not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"text/plain":{"schema":{"type":"string"}}}},"RateLimited":{"description":"The place provider rate limited the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UpstreamUnavailable":{"description":"The upstream place provider is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Bounds":{"type":"array","prefixItems":[{"type":"number","minimum":-180,"maximum":180},{"type":"number","minimum":-90,"maximum":90},{"type":"number","minimum":-180,"maximum":180},{"type":"number","minimum":-90,"maximum":90}],"minItems":4,"maxItems":4},"Coordinate":{"type":"array","prefixItems":[{"type":"number","minimum":-180,"maximum":180},{"type":"number","minimum":-90,"maximum":90}],"minItems":2,"maxItems":2},"Error":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"Place":{"type":"object","required":["id","label","context","coordinate","provider"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"context":{"type":"string"},"coordinate":{"$ref":"#/components/schemas/Coordinate"},"bounds":{"type":"array","items":{"$ref":"#/components/schemas/Coordinate"},"minItems":2,"maxItems":2},"kind":{"type":"string"},"provider":{"type":"string","enum":["openstreetmap"]}}},"PlaceSearchResponse":{"type":"object","required":["attribution","provider","results"],"properties":{"attribution":{"type":"string"},"provider":{"type":"string","enum":["openstreetmap"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Place"}}}},"LegendStop":{"type":"object","required":["color","label"],"properties":{"color":{"type":"string"},"label":{"type":"string"}}},"MrmsLayer":{"type":"object","required":["bounds","caveat","description","id","imageUrl","label","legend","observedAt","opacity","sourceUrl","unit","visible"],"properties":{"bounds":{"$ref":"#/components/schemas/Bounds"},"caveat":{"type":"string"},"description":{"type":"string"},"id":{"type":"string","enum":["mrms-mesh","mrms-reflectivity"]},"imageUrl":{"type":"string"},"label":{"type":"string"},"legend":{"type":"array","items":{"$ref":"#/components/schemas/LegendStop"}},"observedAt":{"type":"string","format":"date-time"},"opacity":{"type":"number","minimum":0,"maximum":1},"sourceLastModified":{"type":"string","format":"date-time"},"sourceUrl":{"type":"string","format":"uri"},"unit":{"type":"string"},"visible":{"type":"boolean"}}},"MrmsManifest":{"type":"object","required":["schemaVersion","mode","attribution","disclosure","generatedAt","layers"],"properties":{"schemaVersion":{"type":"integer","const":1},"mode":{"type":"string","const":"live"},"attribution":{"type":"string"},"disclosure":{"type":"string"},"eventId":{"type":"string"},"revisionId":{"type":"string"},"sequence":{"type":"integer","minimum":1},"generatedAt":{"type":"string","format":"date-time"},"layers":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/MrmsLayer"}}}},"MrmsHistoryFrame":{"type":"object","required":["observedAt","layers"],"properties":{"observedAt":{"type":"string","format":"date-time"},"revisionId":{"type":"string"},"sequence":{"type":"integer","minimum":1,"description":"Monotonic publication sequence within this event; not observation-time order."},"layers":{"type":"array","items":{"$ref":"#/components/schemas/MrmsLayer"}}}},"MrmsHistory":{"type":"object","required":["schemaVersion","mode","attribution","disclosure","frames","generatedAt"],"properties":{"schemaVersion":{"type":"integer","const":1},"mode":{"type":"string","const":"live"},"attribution":{"type":"string"},"disclosure":{"type":"string"},"eventId":{"type":"string"},"eventLabel":{"type":"string"},"frames":{"type":"array","items":{"$ref":"#/components/schemas/MrmsHistoryFrame"},"description":"Bounded newest subset in ascending publication sequence. Several revisions can describe the same observation window."},"generatedAt":{"type":"string","format":"date-time"},"latestSequence":{"type":"integer","minimum":1},"retentionClass":{"type":"string","enum":["benchmark","operational"]}}},"StormEvent":{"type":"object","required":["id","kind","label","status","retentionClass","updatedAt"],"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["historical-event","operational-feed"]},"label":{"type":"string"},"status":{"type":"string","enum":["active","complete","archived"]},"retentionClass":{"type":"string","enum":["benchmark","operational"]},"startedAt":{"type":["string","null"],"format":"date-time"},"endedAt":{"type":["string","null"],"format":"date-time"},"bounds":{"oneOf":[{"$ref":"#/components/schemas/Bounds"},{"type":"null"}],"description":"Null until bounds exist, including newly registered benchmark windows."},"currentRevisionId":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"}}},"StormEventList":{"type":"object","required":["schemaVersion","generatedAt","events"],"properties":{"schemaVersion":{"type":"integer","const":1},"generatedAt":{"type":"string","format":"date-time"},"events":{"type":"array","items":{"$ref":"#/components/schemas/StormEvent"}}}},"DataServiceStatus":{"type":"object","required":["service","generatedAt","products"],"properties":{"service":{"type":"string","const":"stormtrust-data"},"generatedAt":{"type":"string","format":"date-time"},"products":{"type":"array","items":{"$ref":"#/components/schemas/DataProductStatus"}}}},"DataProductStatus":{"type":"object","description":"Latest discovered source asset for a product across operational and benchmark events. Not a completeness or freshness assessment. Property names match the current SQL-backed wire response.","required":["product_id","status","observed_at","completed_at","error_message"],"properties":{"product_id":{"type":"string","enum":["mrms-mesh","mrms-reflectivity"]},"status":{"type":"string","enum":["discovered","queued","processing","complete","failed"]},"observed_at":{"type":["string","null"],"format":"date-time","description":"Source observation time; may be null before processing."},"completed_at":{"type":["string","null"],"format":"date-time","description":"Processing completion time when present; not source freshness."},"error_message":{"type":["string","null"],"description":"Last stored processing error, otherwise null."}}},"StormRevisionReady":{"type":"object","required":["type","schemaVersion","eventId","revisionId","sequence","observedAt","publishedAt","bounds","manifestUrl"],"properties":{"type":{"type":"string","const":"storm.revision.ready"},"schemaVersion":{"type":"integer","const":1},"eventId":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"},"revisionId":{"type":"string"},"sequence":{"type":"integer","minimum":1},"observedAt":{"type":"string","format":"date-time"},"publishedAt":{"type":"string","format":"date-time"},"bounds":{"$ref":"#/components/schemas/Bounds"},"manifestUrl":{"type":"string","pattern":"^/(v1/storm-events/|mrms/events/)","description":"Same-origin manifest path. Read authoritative state after a notice; delivery is not a complete event log."}}},"StreamReady":{"type":"object","required":["type","schemaVersion","eventId"],"properties":{"type":{"type":"string","const":"stream.ready"},"schemaVersion":{"type":"integer","const":1},"eventId":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"},"lastRevision":{"$ref":"#/components/schemas/StormRevisionReady","description":"Omitted when the room has no retained notice. Not a history replay."}}},"StormStreamMessage":{"oneOf":[{"$ref":"#/components/schemas/StreamReady"},{"$ref":"#/components/schemas/StormRevisionReady"}]}}}}