{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"Sargacast API","version":"1.0.0","summary":"Sargassum levels for Florida beaches, as JSON.","description":"Daily sargassum level for Florida beaches on a 0 to 10 Sargassum Meter, with a 7-day outlook per beach. Free, no key, read-only JSON, CORS open (Access-Control-Allow-Origin: *). Responses are cached at the CDN for about 5 minutes; the data is recalculated hourly when the engine runs.\n\nEvery response is an envelope: `{ \"ok\": true, \"data\": ... }` or `{ \"ok\": false, \"error\": { \"code\", \"message\", \"hint\"? } }`.\n\nUsage terms:\n\n- Free to use, including in apps and articles, with the visible credit \"Data: Sargacast (sargacast.com)\" and a link to https://sargacast.com.\n- Please cache responses and avoid more than about one request per minute per client.\n- The data is a guide, not an official safety or health warning.\n- For heavy or commercial use or a higher volume, write to hola@sargacast.com.\n\nEvery page of the site also answers Markdown with `Accept: text/markdown`; see https://sargacast.com/llms.txt.","contact":{"name":"Sargacast","email":"hola@sargacast.com","url":"https://sargacast.com/en/developers"},"license":{"name":"Free use with credit: Data: Sargacast (sargacast.com)","url":"https://sargacast.com/en/developers#terms"},"termsOfService":"https://sargacast.com/en/developers#terms"},"servers":[{"url":"https://sargacast.com","description":"Production"}],"externalDocs":{"description":"Developer guide with examples","url":"https://sargacast.com/en/developers"},"security":[],"tags":[{"name":"beaches","description":"Sargassum readings and outlooks per beach."},{"name":"status","description":"Service health and data freshness."}],"paths":{"/api/v1/beaches":{"get":{"operationId":"listBeaches","summary":"List every beach with its sargassum reading","description":"All active beaches in display order, each with today's 0 to 10 value, its level word, status, confidence and when it was calculated. Filter by zone; ask for tomorrow's forecast with day=tomorrow. Unknown parameters are ignored; an unknown zone returns an empty list.","tags":["beaches"],"parameters":[{"name":"zone","in":"query","required":false,"description":"Only the beaches of this zone (the zone_slug of any beach), e.g. miami-beach.","schema":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":80},"example":"miami-beach"},{"name":"day","in":"query","required":false,"description":"today (default) gives the current reading; tomorrow gives tomorrow's forecast in the same fields (status no_data where a beach has none).","schema":{"type":"string","enum":["today","tomorrow"],"default":"today"},"example":"tomorrow"}],"security":[],"responses":{"200":{"description":"The beaches.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeachListResponse"},"examples":{"ok":{"summary":"Two beaches, one with a reading and one without","value":{"ok":true,"data":[{"slug":"south-pointe","name":"South Pointe","short_name":"South Pointe","zone_slug":"miami-beach","lat":25.7684,"lng":-80.1337,"index_value":1,"level":"clean","status":"ok","confidence":0.82,"computed_at":"2026-10-22T13:00:00+00:00"},{"slug":"palm-beach-midtown","name":"Palm Beach Midtown","short_name":"Midtown","zone_slug":"palm-beach","lat":26.7056,"lng":-80.0364,"index_value":null,"level":null,"status":"no_data","confidence":0,"computed_at":null}]}}}}}},"422":{"description":"A parameter is invalid (code validation_failed); the hint names it and its allowed values.","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"invalidDay":{"summary":"day=yesterday","value":{"ok":false,"error":{"code":"validation_failed","message":"Invalid query parameter: day","hint":"day must be \"today\" (the default) or \"tomorrow\". The public API is described at https://sargacast.com/openapi.json and https://sargacast.com/en/developers."}}}}}}},"500":{"description":"A query failed (code internal).","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"internal":{"summary":"A query failed","value":{"ok":false,"error":{"code":"internal","message":"The query failed"}}}}}}},"503":{"description":"The database did not answer (code unavailable). Retry later.","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"unavailable":{"summary":"The database did not answer","value":{"ok":false,"error":{"code":"unavailable","message":"Database unavailable"}}}}}}}}}},"/api/v1/beaches/{slug}":{"get":{"operationId":"getBeach","summary":"Get one beach with its 7-day outlook","description":"Today's reading for one beach plus its zone name, today's community reports and up to 7 forecast days. The first days (lead_days 0 to 3) are a forecast; the later ones only a trend.","tags":["beaches"],"parameters":[{"name":"slug","in":"path","required":true,"description":"The beach's slug, as listed by listBeaches.","schema":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":80},"example":"south-pointe"}],"security":[],"responses":{"200":{"description":"The beach.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeachDetailResponse"},"examples":{"ok":{"summary":"South Pointe with its 7-day outlook","value":{"ok":true,"data":{"slug":"south-pointe","name":"South Pointe","short_name":"South Pointe","zone_slug":"miami-beach","lat":25.7684,"lng":-80.1337,"index_value":1,"level":"clean","status":"ok","confidence":0.82,"computed_at":"2026-10-22T13:00:00+00:00","zone_name":"Miami Beach","reports_today":4,"forecast":[{"date":"2026-10-22","index_value":1,"level":"clean","confidence":0.8,"lead_days":0,"kind":"forecast"},{"date":"2026-10-23","index_value":2,"level":"clean","confidence":0.72,"lead_days":1,"kind":"forecast"},{"date":"2026-10-24","index_value":2,"level":"clean","confidence":0.64,"lead_days":2,"kind":"forecast"},{"date":"2026-10-25","index_value":3,"level":"clean","confidence":0.56,"lead_days":3,"kind":"forecast"},{"date":"2026-10-26","index_value":4,"level":"moderate","confidence":0.48,"lead_days":4,"kind":"trend"},{"date":"2026-10-27","index_value":4,"level":"moderate","confidence":0.4,"lead_days":5,"kind":"trend"},{"date":"2026-10-28","index_value":3,"level":"clean","confidence":0.32,"lead_days":6,"kind":"trend"}]}}}}}}},"404":{"description":"No beach has this slug, or the slug is malformed (code not_found).","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"unknownBeach":{"summary":"A slug that names no beach","value":{"ok":false,"error":{"code":"not_found","message":"No beach with slug no-such-beach","hint":"List the beach slugs with GET https://sargacast.com/api/v1/beaches (the slug field of each entry). The public API is described at https://sargacast.com/openapi.json and https://sargacast.com/en/developers."}}}}}}},"500":{"description":"A query failed (code internal).","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"internal":{"summary":"A query failed","value":{"ok":false,"error":{"code":"internal","message":"The query failed"}}}}}}},"503":{"description":"The database did not answer (code unavailable). Retry later.","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"unavailable":{"summary":"The database did not answer","value":{"ok":false,"error":{"code":"unavailable","message":"Database unavailable"}}}}}}}}}},"/api/v1/health":{"get":{"operationId":"getHealth","summary":"Check that the data is being served","description":"Answers 200 when the database answers, with the time of the newest calculation across all beaches, so a client can tell how fresh the data is.","tags":["status"],"security":[],"responses":{"200":{"description":"The service is up.","headers":{"Cache-Control":{"$ref":"#/components/headers/CacheControl"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"examples":{"ok":{"summary":"Database up, last calculation this hour","value":{"ok":true,"data":{"computed_at":"2026-10-22T13:00:00+00:00","database":"ok"}}}}}}},"500":{"description":"A query failed (code internal).","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"internal":{"summary":"A query failed","value":{"ok":false,"error":{"code":"internal","message":"The query failed"}}}}}}},"503":{"description":"The database did not answer (code unavailable). Retry later.","headers":{"Cache-Control":{"$ref":"#/components/headers/NoStore"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"unavailable":{"summary":"The database did not answer","value":{"ok":false,"error":{"code":"unavailable","message":"Database unavailable"}}}}}}}}}}},"components":{"schemas":{"Beach":{"type":"object","description":"One beach with today's (or, with day=tomorrow, tomorrow's) reading.","properties":{"slug":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":80,"description":"Stable identifier, used in /api/v1/beaches/{slug}."},"name":{"type":"string","description":"Beach name."},"short_name":{"type":"string","description":"Short name for small screens."},"zone_slug":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":80,"description":"Zone of the beach; filter the list with ?zone=."},"lat":{"type":"number","minimum":-90,"maximum":90,"description":"Latitude (WGS 84)."},"lng":{"type":"number","minimum":-180,"maximum":180,"description":"Longitude (WGS 84)."},"index_value":{"type":["integer","null"],"minimum":0,"maximum":10,"description":"Sargassum Meter from 0 (none) to 10 (heavy). null when the beach has no recent data."},"level":{"type":["string","null"],"enum":["clean","moderate","heavy",null],"description":"The meter in words: clean (0 to 3), moderate (4 to 6) or heavy (7 to 10)."},"status":{"type":"string","enum":["ok","stale","no_data"],"description":"ok: recent reading. stale: the last reading is older than it should be, shown with its age. no_data: nothing recent."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"How much the value can be trusted, from 0 to 1 (more inputs, more recent: higher)."},"computed_at":{"type":["string","null"],"format":"date-time","description":"When the engine last calculated this reading (it runs hourly). Not the satellite pass: the satellite input is a 7-day composite."}},"required":["slug","name","short_name","zone_slug","lat","lng","index_value","level","status","confidence","computed_at"],"additionalProperties":false},"BeachDetail":{"type":"object","description":"One beach with today's reading and its outlook.","properties":{"slug":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":80,"description":"Stable identifier, used in /api/v1/beaches/{slug}."},"name":{"type":"string","description":"Beach name."},"short_name":{"type":"string","description":"Short name for small screens."},"zone_slug":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","maxLength":80,"description":"Zone of the beach; filter the list with ?zone=."},"lat":{"type":"number","minimum":-90,"maximum":90,"description":"Latitude (WGS 84)."},"lng":{"type":"number","minimum":-180,"maximum":180,"description":"Longitude (WGS 84)."},"index_value":{"type":["integer","null"],"minimum":0,"maximum":10,"description":"Sargassum Meter from 0 (none) to 10 (heavy). null when the beach has no recent data."},"level":{"type":["string","null"],"enum":["clean","moderate","heavy",null],"description":"The meter in words: clean (0 to 3), moderate (4 to 6) or heavy (7 to 10)."},"status":{"type":"string","enum":["ok","stale","no_data"],"description":"ok: recent reading. stale: the last reading is older than it should be, shown with its age. no_data: nothing recent."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"How much the value can be trusted, from 0 to 1 (more inputs, more recent: higher)."},"computed_at":{"type":["string","null"],"format":"date-time","description":"When the engine last calculated this reading (it runs hourly). Not the satellite pass: the satellite input is a 7-day composite."},"zone_name":{"type":"string","description":"Zone name."},"reports_today":{"type":"integer","minimum":0,"description":"Community reports for this beach today."},"forecast":{"type":"array","items":{"$ref":"#/components/schemas/ForecastDay"},"description":"Up to 7 days, in order."}},"required":["slug","name","short_name","zone_slug","lat","lng","index_value","level","status","confidence","computed_at","zone_name","reports_today","forecast"],"additionalProperties":false},"ForecastDay":{"type":"object","description":"One day of a beach's 7-day outlook.","properties":{"date":{"type":"string","format":"date","description":"Day in Florida (America/New_York)."},"index_value":{"type":"integer","minimum":0,"maximum":10,"description":"Sargassum Meter from 0 (none) to 10 (heavy)."},"level":{"type":"string","enum":["clean","moderate","heavy"],"description":"The meter in words: clean (0 to 3), moderate (4 to 6) or heavy (7 to 10)."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"How much the value can be trusted, from 0 to 1 (more inputs, more recent: higher)."},"lead_days":{"type":"integer","minimum":0,"description":"Days between the calculation and this day (0 is the same day)."},"kind":{"type":"string","enum":["forecast","trend"],"description":"forecast for lead_days 0 to 3; trend, a rougher outlook, for the later days."}},"required":["date","index_value","level","confidence","lead_days","kind"],"additionalProperties":false},"Health":{"type":"object","description":"Whether the data is being served and how fresh it is.","properties":{"computed_at":{"type":["string","null"],"format":"date-time","description":"The newest calculation across all beaches; null before the first one."},"database":{"type":"string","const":"ok","description":"The database answered."}},"required":["computed_at","database"],"additionalProperties":false},"ApiError":{"type":"object","description":"The error.","properties":{"code":{"type":"string","enum":["validation_failed","not_found","method_not_allowed","unavailable","internal"],"description":"Machine-readable reason."},"message":{"type":"string","description":"What went wrong, in English."},"hint":{"type":"string","description":"What to do next, with a link to the docs. Optional."}},"required":["code","message"],"additionalProperties":false},"ErrorEnvelope":{"type":"object","description":"Every error: an HTTP status plus this body.","properties":{"ok":{"type":"boolean","const":false},"error":{"$ref":"#/components/schemas/ApiError"}},"required":["ok","error"],"additionalProperties":false},"BeachListResponse":{"type":"object","description":"The beaches, in display order.","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Beach"}}},"required":["ok","data"],"additionalProperties":false},"BeachDetailResponse":{"type":"object","description":"One beach.","properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BeachDetail"}},"required":["ok","data"],"additionalProperties":false},"HealthResponse":{"type":"object","description":"Service health.","properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Health"}},"required":["ok","data"],"additionalProperties":false}},"headers":{"CacheControl":{"description":"Cached at the CDN for 5 minutes, served stale for 10 more while it refreshes.","schema":{"type":"string","examples":["public, s-maxage=300, stale-while-revalidate=600"]}},"NoStore":{"description":"Errors are never cached.","schema":{"type":"string","const":"no-store"}}}}}