{"openapi":"3.1.0","info":{"title":"Drinking Water Watch API","version":"1.0.0","description":"Public drinking water records for Mississippi State Department of Health, read from SDWIS/STATE.\n\n**What this is.** The same records the agency publishes on its public drinking water records\npages, served as JSON with codes decoded, missing values reported as missing, and suspect records\nflagged. Nothing here is behind a login, and nothing here goes beyond what those public pages\nalready shows.\n\n**Two places a naive reading goes wrong.**\n\n1. *Violation counts are not a single number.* The same table yields several defensible totals\n   depending on whether grouped violations count as one, whether a\n   violation with no analyte on record counts. `GET /v1/water-systems/{pwsid}/summary` returns them\n   all side by side. Quote one only with its reading attached.\n2. *An aggregate is not a statement about the system in front of you.* The mix of\n   violation categories across the whole register is not the mix for any one system, and\n   the two can differ completely. Check `category` on the rows you actually fetched\n   before characterising them.\n\n**Codes.** Every coded field arrives as `{ code, label, labelSource }`. `labelSource` tells you\nhow far to trust the label: `sdwis-code-table` is the agency's own wording, `dww-help`\nis quoted from the help topics that ship inside Drinking Water Watch and are linked from every\nrecords page — authoritative for what a field is CALLED, not for what it means —\n`epa-standard` is federally defined, `curated-unverified` means no authoritative\ndecode exists anywhere and this API inferred it, and `no-decode-available` means the label is\nnull because nothing decodes that code at all. Attribute a `sdwis-code-table` label to the\nagency. Do not attribute a `curated-unverified` one to anybody but this API, or a\n`dww-help` one to anybody but the application it ships in.\n\n**Requests are recorded.** This installation records each request to `/v1`: the route, the query, the response status, how long it took, and the client name the request sent. It records a scrambled identifier derived from the network address, which changes every day, and never the address itself. Records are kept for 90 days and then deleted.\n\n**Request limit.** This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.\n\n**Missing data.** SDWIS records \"not recorded\" as a value, not as null — a date of 1901-01-01, a\nmeter size of 0. This API reports those as `null` and explains them in `dataQuality`. A null here\nmeans the agency has no value on record, not that the value is zero.\n\n**Scope.** This API is capped at what the agency's existing public application publishes — records held in SDWIS but not surfaced there are out of scope and will never appear. It does not yet serve all of it: see coverage.notYetServed for areas the agency's own page publishes that this API does not. Distinguish the two absences. An empty collection means nothing matched that collection's scope — its reading or scope says what that is, and it is often narrower than every record the agency holds — not that the agency has no such record. A missing endpoint means this API does not carry that record type, which is not evidence the records do not exist. Never report either as an absence of fact."},"components":{"schemas":{"CodedValue":{"type":["object","null"],"description":"A value stored as a code, with its decoded label and the provenance of that label.","properties":{"code":{"type":"string","description":"The raw SDWIS code, exactly as stored. Stable; safe to compare against."},"label":{"type":["string","null"],"description":"Human-readable meaning of the code, or null when this installation has no decode for it. Never guess a label from the code."},"labelSource":{"type":"string","enum":["sdwis-code-table","dww-help","epa-standard","curated-unverified","no-decode-available"],"description":"How far to trust the label. \"sdwis-code-table\" comes from this agency's own code table and is authoritative. \"dww-help\" is quoted from the help topics that ship inside Drinking Water Watch, which are published — every records page links to them — but are the application's text, not the agency's; treat it as authoritative for what a field is CALLED and not for what it means, and the domain listing in /v1/code-tables names the file it came from. \"epa-standard\" is federally defined. \"curated-unverified\" was inferred by this API because no authoritative decode exists anywhere — do not present it as the agency's wording. \"no-decode-available\" means the label is null: the code is in the records and nothing anywhere decodes it, so report the bare code."}},"required":["code","label","labelSource"]},"DataQuality":{"type":"array","description":"Problems detected in the underlying records. Nothing here has been corrected — the API flags and reports, because whether a suspect record is an error is the agency's determination. An empty array means no known problems, not that the data is verified.","items":{"type":"object","properties":{"field":{"type":"string","description":"Field this concerns."},"code":{"type":"string","description":"Stable machine-readable issue code, safe to branch on."},"issue":{"type":"string","description":"What is wrong, in a sentence."}},"required":["field","code","issue"]}},"Meta":{"type":"object","description":"Provenance for this response. Present on every payload so a consumer can attribute what it repeats. Deliberately carries no timestamp: you know when you made the request, the records are live as of it, and if this response reached you from a cache that is what the HTTP Date and Cache-Control headers are for.","properties":{"source":{"type":"object","properties":{"agency":{"type":"string","description":"Primacy agency that maintains these records."},"system":{"type":"string","description":"System of record the data was read from."}},"required":["agency","system"]},"apiVersion":{"type":"string","description":"Shape version of this API, semver, matching the /v1 segment in the path and the API-Version response header. Quote it when you quote the data.\n\nDO NOT derive one URL from another by editing this segment, and do not assume the public page shares it: the page for a system is UNVERSIONED and lives on a different host. Wherever a page exists, the response's Link header names it with rel=\"alternate\"; meta.humanUrl repeats it in the body only on deployments whose site is publicly reachable. Follow those rather than rewriting an address.\n\nMINOR increments add keys or endpoints and do not move the path. MAJOR increments remove or rename something and get a new path segment. A CORRECTED FACT increments neither: a label found to be wrong is fixed in place, in every version, so pinning a version never pins you to a falsehood — and a value that changed is not evidence the contract broke."},"humanUrl":{"type":"string","format":"uri","description":"The page a person reads for this water system. Cite it when answering someone from this data, so the reader lands on the agency record rather than a copy of it. OFTEN ABSENT, and its absence is deliberate rather than an oversight: it appears only when the payload is about a single system AND this deployment has a publicly reachable UI. ITS ABSENCE DOES NOT MEAN THERE IS NO PAGE: the response's Link header carries rel=\"alternate\" to the page whenever this deployment has a site, public or not — read the header before concluding there is none. Where neither exists, name the agency and its public drinking water records site, and do not construct a URL. Check for the key; two arrival tests spent requests hunting for a field this text had promised unconditionally."},"limits":{"type":"string","description":"The one thing that must survive being the only sentence read: these are regulatory records as the agency reports them, and neither an empty result nor an unserved record type is evidence that a fact is absent.\n\nWRITTEN FOR YOU, NOT FOR A MEMBER OF THE PUBLIC. Carry its SUBSTANCE into whatever you show a person; do not paste the WORDING. It talks about empty results and unserved record types, which are facts about this API rather than about anyone's water. /coverage says in its `audience` field which of its strings may go in front of a person, and `forAPerson` is currently EMPTY on purpose: this API publishes no sentence written for the public. This description said \"repeat its substance\" and nothing else for several days, and a renderer reasonably pasted the string onto a public page."},"coverage":{"type":"string","format":"uri","description":"Where the full text behind `limits` lives: the scope statement, the disclaimer, and both coverage lists enumerated — what the agency's public application publishes that this API does not serve, and what neither publishes. Fetch it before reporting that a record does not exist. It does not change between responses, which is why it is a URL here rather than 1.6 KB of repeated text on every payload."}},"required":["source","limits","coverage"]},"PageInfo":{"type":"object","description":"Whether this is a page or the whole thing, and where in the record it sits. Present on EVERY collection — read `paginated` first.","properties":{"paginated":{"type":"boolean","description":"Whether this endpoint pages at all. FALSE means it takes no limit or offset and has returned every row it matched in one response — which is every row it PUBLISHES within any window its `window` states, not necessarily every row the agency holds: any fixed selection is stated in the collection's reading or scope. limit and offset are null rather than invented, because a limit of 58 on a 58-row answer cannot be told from a limit that was applied. TRUE means limit and offset were honoured and hasMore says whether more rows exist. Many collections here are unpaginated, and this field is how each one says so."},"total":{"type":"integer","description":"Total rows matching the filters, across all pages."},"limit":{"type":["null","integer"],"description":"Rows requested for this page; null when this endpoint does not page."},"offset":{"type":["null","integer"],"description":"Rows skipped before this page; null when this endpoint does not page."},"returned":{"type":"integer","description":"Rows actually in this page."},"hasMore":{"type":"boolean","description":"Whether more rows match beyond this page. Check this before concluding a list is complete. Always false when paginated is false. When true, the response's Link header carries rel=\"next\" — this request with every parameter you sent and the offset moved on — and any page after the first carries rel=\"prev\"."},"nextOffset":{"type":["null","integer"],"description":"THE NEXT PAGE, IN THE BODY: send this same request again with offset set to this value — every other parameter unchanged. Null when hasMore is false, and always null when paginated is false. It is the same page the Link header's rel=\"next\" names, for a reader that never sees headers."},"prevOffset":{"type":["null","integer"],"description":"The previous page, the same way: this request with offset set to this value. Null on the first page and when paginated is false."}},"required":["paginated","total","limit","offset","returned","hasMore","nextOffset","prevOffset"]},"Problem":{"type":"object","description":"Error response.","properties":{"error":{"type":"string","description":"Stable machine-readable error code."},"message":{"type":"string","description":"What went wrong and, where possible, how to fix it."}},"required":["error","message"]},"Pwsid":{"type":"string","description":"Public Water System ID — the public identifier for a water system: two characters naming the primacy agency, then seven digits, e.g. MS0010002. This is the only water system key this API accepts or returns; SDWIS internal surrogate keys are never exposed.","pattern":"^[A-Za-z0-9]{2}[0-9]{7}$"}}},"paths":{"/":{"get":{"summary":"What this service is, and a priced directory of everywhere else to go","tags":["discovery"],"description":"The entry point for a client with nothing but the origin. Names the service and its identifier, then lists every other discovery document with its size and when you would want it. Deliberately small: fetch it first, then fetch only what your question needs.","responses":{"200":{"description":"Default Response"}}}},"/coverage":{"get":{"summary":"What is served, what is not, and what neither this API nor the agency publishes","tags":["discovery"],"description":"The full statement behind the one-line `limits` on every response. Fetch it before reporting that a record does not exist: an empty collection means the agency has no such record, an unserved record type means this API does not carry it, and only one of those is evidence about the world.","responses":{"200":{"description":"Default Response"}}}},"/openapi/index.json":{"get":{"summary":"Every endpoint in one line each, and where to read more about it","tags":["discovery"],"description":"An index of the API. Each entry names an endpoint, says what it answers, and links to a standalone OpenAPI document for just that endpoint. Fetch this first, then fetch only the descriptions you need — several at once with a comma, e.g. /openapi/summary,violations.json.","responses":{"200":{"description":"Default Response"}}}},"/openapi/{slice}.json":{"get":{"summary":"A standalone OpenAPI document for one or more endpoints","tags":["discovery"],"description":"Names come from /openapi/index.json — an endpoint name, a group name, or several of either separated by commas. Components are pruned to what the selected paths reference, so the result is a complete document rather than an excerpt.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slice","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/openapi.json":{"get":{"summary":"OpenAPI description of this API","tags":["discovery"],"description":"The conventional location. Identical to /docs/json.","responses":{"200":{"description":"Default Response"}}}},"/llms.txt":{"get":{"summary":"Orientation for an AI agent, in prose","tags":["discovery"],"description":"What this service covers, how to call it, and the ways a naive reading goes wrong.","responses":{"200":{"description":"Default Response"}}}},"/.well-known/api-catalog":{"get":{"summary":"API catalogue (RFC 9727)","tags":["discovery"],"responses":{"200":{"description":"Default Response"}}}},"/robots.txt":{"get":{"summary":"Crawler policy","tags":["discovery"],"responses":{"200":{"description":"Default Response"}}}},"/v1/water-systems":{"get":{"summary":"Search water systems","tags":["water systems"],"description":"Find water systems by name, PWSID, county, type, source or status. Every filter is optional and they combine with AND. Results are always paged — check page.hasMore before concluding a list is complete. To look up one known system, call GET /v1/water-systems/{pwsid} instead.","parameters":[{"schema":{"type":"string"},"in":"query","name":"q","required":false,"description":"Case-insensitive substring match against the system NAME, its PWSID, or the PRINCIPAL COUNTY it serves. All three, in one parameter, because a caller who has been handed a string does not yet know which of the three it is — that is what this door is for. When you already know a value is a county, `county` takes an exact one from /water-systems/search-options and does not also match names.\n\nThe county arm means a row can match for a reason the row does not show: ?q=ADAMS returns every system in Adams County alongside any system NAMED Adams, and each row's matchedOn says which. To match counties only, use `county`.\n\nEach row's matchedOn says which of the three it matched."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Principal county served. This is a CONSTRAINED value, not free text: GET /water-systems/search-options returns the exact list this accepts, and passing anything else silently matches nothing. Fetch the list first rather than guessing a spelling — \"Adams\", \"ADAMS COUNTY\" and \"Adams Co\" are not interchangeable. Matched case-insensitively against the county name as the agency records it."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Federal system type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists every value this accepts, GET /v1/code-tables/fedType decodes them."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Primary source code, e.g. GW for ground water. Constrained: GET /water-systems/search-options lists every value this accepts, GET /v1/code-tables/primarySource decodes them."},{"schema":{"type":"string"},"in":"query","name":"status","required":false,"description":"Activity status code, e.g. A for active. Omit to include inactive systems. Constrained: GET /v1/code-tables/activityStatus lists every value this accepts."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page, up to 500."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Rows to skip."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string","description":"Plain-language statement of exactly which systems this total is of. Quote it beside any number you take from page.total — this is the total most likely to be repeated as a fact about the agency, and what it counts is not obvious from the number."},"matchedOnCounts":{"type":"object","description":"How the whole result — not this page — splits by which fields your `q` MATCHED. Present only when you sent `q`. Each key is a row's `matchedOn` joined with a comma — \"county\", \"name\", \"name,county\" — and each value is how many systems matched on exactly that combination, so the values add up to page.total and a system is counted once. `q` searches name, PWSID and county at once, and the rows stay in the agency's own order. So ?q=jackson lists systems named for Jackson among every system in Jackson County, and this says how many of each you are looking at before you page. Counts, not a ranking: the agency publishes no basis for calling one kind of match better.","additionalProperties":{"type":"integer"}},"filtersApplied":{"type":"object","description":"Which of your filters actually did anything. Present whenever you sent a filter parameter at all, including one with an empty value. An unknown PARAMETER is a 400 here, and so is a VALUE outside a closed vocabulary (county, fedType, source, status) — refused with the values that filter does accept, because it used to be a silent 200 with zero rows, indistinguishable from a real answer. `q` is free text and is never refused.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"},"description":"The filters the statement was given, trimmed, EXCLUDING any listed under ignored. `county` is echoed as the AGENCY records it, taken from the rows that came back, because the filter matches without regard to case and a client grouping by this value would otherwise accumulate a key per spelling. Where no row came back there is nothing to canonicalise against and your own value is echoed. The coded filters are compared exactly and are echoed as you sent them.\n\nAn EMPTY object is a real and loud answer: nothing you sent was applied."},"ignored":{"type":"array","description":"Parameters you sent with an empty value; absent, not empty, when you sent none. An empty value is not a filter here: it is treated exactly as if the parameter had not been sent. It is reported whether or not rows came back, because an ignored filter misleads most when rows do come back. A template such as `&status=${status}` with an unset variable sends an empty value, and this says it was ignored.","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"waterSystems":{"type":"array","items":{"type":"object","description":"Summary row. Fetch GET /v1/water-systems/{pwsid}/summary for detail.","properties":{"pwsid":{"type":"string","description":"Public Water System ID."},"name":{"type":["null","string"],"description":"Legal name of the water system."},"federalType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Federal system type, which determines the applicable regulations. Null when the underlying record has no value for this field."},"stateType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The primacy agency's own classification (SDWIS calls it State Type), set independently of the federal type. Null when the underlying record has no value for this field."},"primarySource":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Predominant source of the water delivered. Null when the underlying record has no value for this field."},"activityStatus":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the system is currently operating. Null when the underlying record has no value for this field."},"principalCountyServed":{"type":["null","string"],"description":"County the system principally serves."},"populationServed":{"type":"object","description":"Total population served, as one number, so systems can be compared and ranked in a single request. When `value` is non-null it is the authoritative total: it equals the sum of the system's effective annual operating periods, which is the figure the agency's public application displays. Quote it. Fetch GET /v1/water-systems/{pwsid}/operating-periods when you need the BREAKDOWN rather than the total — that is a list, one row per operating period per population type (residential, transient), and it is the only place the split appears.","properties":{"value":{"type":["null","integer"],"description":"Null when the agency does not maintain this figure for the system. That is not a population of zero, and it is not a system that serves nobody — see dataQuality. Some systems with a null here still have operating periods on record; check there before reporting the population as unknown."},"basis":{"type":"string","enum":["sdwis-derived-total","sdwis-operating-period"],"description":"Which population figure this is; the two are different measurements. sdwis-derived-total is SDWIS's own roll-up across the system's operating periods, the column search ranks on. sdwis-operating-period is one annual operating period, which the agency's page displays; read populationType with it. Where the two disagree, this field says which you are holding. Always quote it with the number."},"populationType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Which population an operating-period figure counts. Most systems report a residential count; non-community systems report a transient or non-transient one, and the two are not comparable. Absent on a derived total, which is a roll-up across all types. Null when the underlying record has no value for this field.","type":["object","null"]},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["value","basis"]},"violations":{"type":"object","description":"One field from this system's /summary violations block, so that \"which systems here have had a violation since some date\" is one search rather than one /summary per system. Counts and categories stay on /summary.","properties":{"lastDeterminedOn":{"type":["null","string"],"format":"date","description":"The newest determination date among this system's VALIDATED violations. Null when the agency has no validated violation on record for the system; rejected records never count. It dates the record, not the request, and it is the same value on /summary and on every search row that carries it."}},"required":["lastDeterminedOn"]},"matchedOn":{"type":"array","items":{"type":"string","enum":["name","pwsid","county"]},"description":"Which field your `q` MATCHED on this row — present only when you sent `q`, and never empty. `q` searches three fields at once, so a single string can return rows for unrelated reasons: ?q=jackson returns systems NAMED for Jackson alongside every system in Jackson COUNTY, and without this you cannot tell them apart. A row can match more than one field. This is not a ranking and the order of this array is not one: the agency publishes no basis for calling a county match worse than a name match, and rows stay in the agency's own order. To search one field deliberately, use `county` with a value from /water-systems/search-options."}},"required":["pwsid"]}}},"required":["meta","page","waterSystems"]}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}":{"get":{"summary":"One water system","tags":["water systems"],"description":"The identifying record for a single water system — the same eight fields the agency's own page shows in its header. For compliance posture and population in one call, use /summary instead.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"waterSystem":{"type":"object","properties":{"pwsid":{"type":"string","description":"Public Water System ID."},"name":{"type":["null","string"],"description":"Legal name of the water system."},"federalType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Federal system type, which determines the applicable regulations. Null when the underlying record has no value for this field."},"stateType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The primacy agency's own classification (SDWIS calls it State Type), set independently of the federal type. Null when the underlying record has no value for this field."},"primarySource":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Predominant source of the water delivered. Null when the underlying record has no value for this field."},"activityStatus":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the system is currently operating. Null when the underlying record has no value for this field."},"principalCountyServed":{"type":["null","string"],"description":"County the system principally serves."},"activityDate":{"type":["null","string"],"format":"date","description":"Date the current activity status took effect, or null when not recorded. SDWIS stores \"not recorded\" as 1901-01-01; this API reports null and explains it in dataQuality rather than passing a placeholder date through as fact."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["pwsid","dataQuality"]},"links":{"type":"array","description":"Where everything else about this system is, with a line on what each holds. The record itself is deliberately one query; summary is first because it answers most questions in one call.","items":{"type":"object","properties":{"rel":{"type":"string"},"href":{"type":"string"},"holds":{"type":["null","string"]}},"required":["rel","href","holds"]}}},"required":["meta","waterSystem","links"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/search-options":{"get":{"summary":"The values each search filter accepts","tags":["water systems"],"description":"What to pass to GET /v1/water-systems. Each block names where its list comes from: the agency's public records search where it offers that choice (county, system type, source), and the application's Help where it does not (status). Read this before filtering: a value the installation cannot match is refused with a 400 that lists the values it can.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"note":{"type":"string"},"county":{"type":"object","description":"Pass one of these verbatim as ?county=.","properties":{"parameter":{"type":"string"},"source":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},"federalType":{"type":"object","additionalProperties":true},"activityStatus":{"type":"object","additionalProperties":true},"primarySource":{"type":"object","additionalProperties":true}},"required":["meta","county","federalType","activityStatus","primarySource"]}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/{slug}":{"get":{"summary":"Not a route — the 404 for anything that does not hang off a water system","tags":["water systems"],"description":"There is no such thing as this URL. A water system has exactly one address, GET /v1/water-systems/{pwsid}, and a fixed set of sub-resources, which this endpoint's 404 body lists. This endpoint exists only so that asking for something else returns an explanation instead of a bare 404: it names every sub-resource that does exist and states, explicitly, that a 404 here means this API does not carry that kind of record — NOT that the system has none of them. Never report the difference as an absence of fact.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true},{"schema":{"type":"string"},"in":"path","name":"slug","required":true,"description":"Any segment that is not one of the published sub-resources."}],"responses":{"404":{"description":"A 404 here means this API does not carry that kind of record at all — it is not a statement that the system has none.","content":{"application/json":{"schema":{"type":"object","description":"A 404 here means this API does not carry that kind of record at all — it is not a statement that the system has none.","properties":{"error":{"type":"string"},"message":{"type":"string"},"waterSystem":{"type":"string","description":"The system itself, if it exists."},"subResources":{"type":"array","description":"Everything that legitimately hangs off this water system.","items":{"type":"string"}},"coverage":{"type":"object","description":"What this API serves, what the public application serves that it does not, and what neither publishes. Read this before reporting anything as absent.","additionalProperties":true}},"required":["error","message"]}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/summary":{"get":{"summary":"One system at a glance: identity, population, sources, violation counts","tags":["water systems"],"description":"The answer-shaped endpoint. Returns identity, population served, water sources and the full violation count breakdown together, so a question like \"tell me about this water system\" is one request rather than six.\n\n**It is an overview, not the whole record.** It carries no sample results, detection figures, lead and copper results, monitoring schedules or contacts. notCarriedHere names each of those collections with its URL.\n\nRead violations.counts carefully before quoting a number: the same table yields several defensible totals, and they are all reported side by side precisely so that no single one gets quoted as if it were the only answer.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"notCarriedHere":{"type":"object","description":"What this overview does not carry (sample results, detections, lead and copper, monitoring schedules, contacts) and the collection that holds each. A summary with nothing in it says nothing about the water.","properties":{"note":{"type":"string"},"collections":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","description":"The collection's name, which is also the last segment of its URL."},"holds":{"type":"string"},"url":{"type":"string"}},"required":["resource","holds","url"]}}},"required":["note","collections"]},"waterSystem":{"type":"object","properties":{"pwsid":{"type":"string","description":"Public Water System ID."},"name":{"type":["null","string"],"description":"Legal name of the water system."},"federalType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Federal system type, which determines the applicable regulations. Null when the underlying record has no value for this field."},"stateType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The primacy agency's own classification (SDWIS calls it State Type), set independently of the federal type. Null when the underlying record has no value for this field."},"primarySource":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Predominant source of the water delivered. Null when the underlying record has no value for this field."},"activityStatus":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the system is currently operating. Null when the underlying record has no value for this field."},"principalCountyServed":{"type":["null","string"],"description":"County the system principally serves."},"activityDate":{"type":["null","string"],"format":"date","description":"Date the current activity status took effect, or null when not recorded. SDWIS stores \"not recorded\" as 1901-01-01; this API reports null and explains it in dataQuality rather than passing a placeholder date through as fact."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["pwsid","dataQuality"]},"populationServed":{"type":"object","description":"Average daily population served for the current annual operating period — the figure the public application displays. Not a census count. The SAME shape the search row carries, so that basis tells you which of the two figures you have: here it is always sdwis-operating-period, and search is always the derived total. Read populationType with it.","properties":{"basis":{"type":"string","enum":["sdwis-derived-total","sdwis-operating-period"],"description":"Which population figure this is; the two are different measurements. sdwis-derived-total is SDWIS's own roll-up across the system's operating periods, the column search ranks on. sdwis-operating-period is one annual operating period, which the agency's page displays; read populationType with it. Where the two disagree, this field says which you are holding. Always quote it with the number."},"populationType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Which population an operating-period figure counts. Most systems report a residential count; non-community systems report a transient or non-transient one, and the two are not comparable. Absent on a derived total, which is a roll-up across all types. Null when the underlying record has no value for this field.","type":["object","null"]},"operatingPeriod":{"type":["null","object"],"description":"The recorded annual operating period this figure comes from, as months and days — seasonal, so it carries no year. Null when the system has no effective operating period on record.","properties":{"startMonth":{"type":["null","integer"]},"startDay":{"type":["null","integer"]},"endMonth":{"type":["null","integer"]},"endDay":{"type":["null","integer"]}}},"activityStatus":{"type":"object","description":"Present only when the system is not active: the agency's activity status and the date it records for it, beside the figure, so a population on an inactive system is never read as a present-day one. The same values as waterSystem.activityStatus and activityDate.","properties":{"code":{"type":["null","string"]},"label":{"type":["null","string"]},"labelSource":{"type":"string"},"since":{"type":["null","string"],"format":"date","description":"The activity date the agency records; null where it is not recorded."}}},"window":{"type":"object","description":"The date that chose the operating period, declared as the filter it is. It does not date the population and does not say the system operates today — activityStatus says that.","properties":{"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["asOf","source"]},"note":{"type":"string","description":"What populationType is not. It is the kind of population counted in this operating period, not the kind of area the system serves. That is a separate agency record, at /water-systems/{pwsid}/service-areas, which can disagree with this one and can list several classes. Always present."},"value":{"type":["null","integer"],"description":"Null when this system has no effective annual operating period on record. That is not a population of zero and not a system serving nobody — it is an absent record. The search row carries the derived total for the same system and is sometimes populated when this is not; the two are different measurements, so say which one you quoted."}},"required":["value","basis"]},"siteVisits":{"type":"object","description":"Inspections and sanitary surveys, rolled up. The record type the agency uses to answer \"has anyone looked at this system, and what did they find\". Read deficienciesOutstanding before saying a system has unresolved problems, and read the note before reading a zero.","properties":{"count":{"type":"integer","description":"Completed visits on record. Zero means the agency has no completed visit on record."},"lastVisit":{"type":["null","string"],"description":"Date of the most recent completed visit (ISO 8601)."},"lastVisitHighestDeficiency":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Severity of the worst finding at the most recent visit, INCLUDING recommendations — so this can be REC while deficienciesFound stays 0. Null when the underlying record has no value for this field.","type":["object","null"]},"deficienciesFound":{"type":"integer","description":"MINOR and SIGNIFICANT deficiencies across every visit. Excludes recommendations, matching the agency's own count."},"deficienciesResolved":{"type":"integer","description":"How many of those the agency has recorded as resolved."},"deficienciesOutstanding":{"type":"integer","description":"Still open. Derived here; the agency publishes the two counts, not the difference."},"all":{"type":"string","description":"The request that returns every visit individually, with its date and its own counts."},"note":{"type":"string"}},"required":["count","deficienciesFound","deficienciesResolved","deficienciesOutstanding","all","note"]},"sources":{"type":"object","description":"Water sources on record, crossed by type and status. Read byType, not the totals: a system can hold two dozen sources and have only one live one, and which TYPE is the live one is the answer to where its water comes from.","properties":{"active":{"type":"integer","description":"Sources with an active status."},"total":{"type":"integer","description":"All sources on record, active or not."},"byType":{"type":"array","description":"One entry per source type present, active count first. A type whose active count is 0 is on record and not in use — retired wells stay on the record permanently, so their presence is not evidence the system draws from them.","items":{"type":"object","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The facility type. Null when the underlying record has no value for this field."},"active":{"type":"integer","description":"Of this type, how many are active."},"total":{"type":"integer","description":"Of this type, how many are on record."}},"required":["type","active","total"]}},"note":{"type":"string","description":"What this count excludes, and what it is therefore NOT. A composed endpoint inherits its components' data and not their disclosures; this block carried the number and none of the caveat."}},"required":["active","total","byType","note"]},"serviceConnections":{"type":"object","description":"Service connections, summed. The agency's own page prints the rows broken out by connection type and meter type and prints no total, so every consumer that wants the headline figure derives it — this is that sum, computed once here. It is a count of CONNECTIONS, not of people: read populationServed for that.","properties":{"total":{"type":"integer","description":"Sum of the per-row connection counts the agency publishes."},"rowsWithNoCount":{"type":"integer","description":"How many rows carried no count and contributed nothing to the total. Above zero means the total is a floor, not a complete figure."},"all":{"type":"string","description":"The request that returns the rows this total is a sum of."},"note":{"type":"string"}},"required":["total","rowsWithNoCount","all","note"]},"purchases":{"type":"object","description":"How many water purchases the agency has on record for this system, so a consumer can tell whether the collection is worth fetching without fetching it. Asked for by the site that renders these records, which was requesting purchases on every page view: most systems buy no water from another, so most of those requests returned nothing. It costs no extra statement here -- it rides on the header query this summary already runs.","properties":{"total":{"type":"integer","description":"Purchase rows on record. Zero is the ordinary case and means the agency records no purchases, not that the system is self-supplied."},"all":{"type":"string","description":"The request that returns the rows this total counts."},"note":{"type":"string"}},"required":["total","all","note"]},"violations":{"type":"object","properties":{"lastDeterminedOn":{"type":["null","string"],"format":"date","description":"The newest determination date among this system's VALIDATED violations. Null when the agency has no validated violation on record for the system; rejected records never count. It dates the record, not the request, and it is the same value on /summary and on every search row that carries it. Here it is the latest of byCategory.categories[].lastDeterminedOn."},"byCategoryNote":{"type":"string","description":"That byCategory partitions counts.validated, and what lastDeterminedOn is."},"byCategory":{"type":"object","description":"The other axis, and the one `counts` does not have. counts counts RECORDS; this splits the same validated records by federal category. The two rankings do not agree: a system can lead on total violations and have a quite different profile by category. Read this before saying any system has the worst record of anything.","properties":{"categories":{"type":"array","items":{"type":"object","properties":{"category":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The federal category, decoded. Null when the underlying record has no value for this field."},"validated":{"type":"integer","description":"Validated violations in this category, grouped and ungrouped alike."},"validatedUngrouped":{"type":"integer","description":"The same, excluding those inside a violation group."},"groups":{"type":"integer","description":"Violation groups in this category."},"rejectedNotViolations":{"type":"integer","description":"Records the agency REJECTED. Never add these to any other count in this object — they are not violations."},"lastDeterminedOn":{"type":["null","string"],"description":"Most recent determination date among the validated records in this category."},"query":{"type":"string","description":"The request that returns these rows. It returns the count named by `reproduces` — NOT necessarily the first number in this row."},"reproduces":{"type":"string","description":"WHICH of the counts in this row `query` returns. The row publishes four numbers and the link returns one of them: `/violations` with no `grouped=true` gives the ungrouped reading, so a row headed validated 88 has a query that returns 2. Stated rather than left to be inferred, because on most rows the two readings are equal and the difference is invisible."}},"required":["category","validated","validatedUngrouped","groups","rejectedNotViolations","lastDeterminedOn","query","reproduces"]}},"note":{"type":"string"}},"required":["categories","note"]},"counts":{"type":"object","description":"Every defensible count of this system's violations. They differ because the underlying records differ in status, in grouping, and in whether an analyte is recorded — not because any of them is wrong.","properties":{"individualUngroupedWithAnalyte":{"type":"object","description":"The narrowest reading: validated status, not part of a violation group, and an analyte on record. No count on the ladder equals what the agency's page shows: that page renders a Group Violations table above the individual one, and the groups are counted separately, in groups.","properties":{"count":{"type":"integer"},"query":{"type":"string","description":"The request that returns exactly this set, so that reading a count and fetching the rows behind it does not require a trip through the OpenAPI description."}},"required":["count","query"]},"validatedUngrouped":{"type":"object","description":"Validated, ungrouped violations, whether or not an analyte is recorded. What GET /v1/water-systems/{pwsid}/violations returns by DEFAULT.","properties":{"count":{"type":"integer"},"query":{"type":"string","description":"The request that returns exactly this set, so that reading a count and fetching the rows behind it does not require a trip through the OpenAPI description."}},"required":["count","query"]},"validatedWithAnalyte":{"type":"object","description":"Validated violations with an analyte on record, including grouped ones.","properties":{"count":{"type":"integer"},"query":{"type":"string","description":"The request that returns exactly this set, so that reading a count and fetching the rows behind it does not require a trip through the OpenAPI description."}},"required":["count","query"]},"validated":{"type":"object","description":"Every violation the agency validated, each member of a violation group counted separately, and those with no analyte recorded. This is the widest HONEST answer to \"how many violations has this system had\". It does not say how many are outstanding — this API computes no compliance status; the enforcement actions the agency took are on each row of the violations list.","properties":{"count":{"type":"integer"},"query":{"type":"string","description":"The request that returns exactly this set, so that reading a count and fetching the rows behind it does not require a trip through the OpenAPI description."}},"required":["count","query"]},"groups":{"type":"object","description":"**Not on the ladder: a different unit.** A violation group is the agency's own unit of one event: several violations determined together under one analyte group. The agency's own page gives each group a single row in a Group Violations table above the individual violations. Counting the members as separate events overstates how many times something happened: compare count with memberViolations.","properties":{"count":{"type":"integer","description":"How many validated violation groups this system has."},"memberViolations":{"type":"integer","description":"How many violation records those groups hold between them."},"members":{"type":"string","description":"The request that returns those member violations."},"note":{"type":"string"}},"required":["count","memberViolations","members","note"]},"rowsWithGroupsAsOne":{"type":"object","description":"What the agency's own page shows, expressed as ROWS rather than a count, because that is what it is: one row per violation group plus one per ungrouped validated violation with an analyte. Two units added together. Use it to check this API against the public site; never as an answer to \"how many violations does this system have\".","properties":{"rows":{"type":"integer","description":"Total rows on the public page: groupRows + individualRows."},"groupRows":{"type":"integer","description":"Rows in the Group Violations table — one per group."},"individualRows":{"type":"integer","description":"Rows in the Individual Violations table — one per violation."},"note":{"type":"string"}},"required":["rows","groupRows","individualRows","note"]},"rejectedNotViolations":{"type":"object","description":"**Not on the ladder, and never to be added to it.** Records the agency rejected: it did not accept them as violations, and its page never shows them. They are often the most recent rows in the table, but they are not recent violations.","properties":{"count":{"type":"integer"},"query":{"type":"string","description":"The request that returns exactly this set, so that reading a count and fetching the rows behind it does not require a trip through the OpenAPI description."}},"required":["count","query"]},"agencyCaveat":{"type":"object","description":"The agency's own caveat about this list, quoted verbatim from the Help topic that ships with Drinking Water Watch, with the file it came from. Carried rather than paraphrased: a caveat this API composes is a claim this API is making, and this one is the agency's. Repeat it as theirs.","properties":{"text":{"type":"string"},"source":{"type":"string"}},"required":["text","source"]},"ladder":{"type":"array","items":{"type":"string"},"description":"The rungs, named, from the narrowest reading to the widest, with the two middle rungs unordered against each other. Rung 1 is inside rungs 2 and 3, and both are inside rung 4, but neither of rungs 2 and 3 contains the other, so the counts can decrease along this list for some systems. Do not sort by value; the order is what the names mean.\n\n**Read this list rather than iterating the object.** Every other key carrying a count counts something else (a group is not a violation, and neither is a rejected record), and placing one on this scale is the misreading the ladder exists to prevent. Object key order is not guaranteed; this list is."},"notOnTheLadder":{"type":"array","items":{"type":"string"},"description":"Keys here that carry a number and are NOT readings of \"how many violations does this system have\". Published so the exclusion is data rather than something each consumer has to enumerate for itself and get wrong once per addition. Every count in this object appears in exactly one of ladder and notOnTheLadder, and a test enforces it."},"defaultReading":{"type":"string","description":"The rung GET /v1/water-systems/{pwsid}/violations returns when given no parameters."},"note":{"type":"string","description":"How to quote these numbers without misleading anyone."}},"required":["individualUngroupedWithAnalyte","validatedUngrouped","validatedWithAnalyte","validated","groups","rejectedNotViolations","rowsWithGroupsAsOne","ladder","notOnTheLadder","defaultReading","note"]}},"required":["counts","byCategory"]}},"required":["meta","waterSystem","populationServed","sources","violations"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/violations":{"get":{"summary":"Violations for one system","tags":["water systems"],"description":"Drinking water regulation violations recorded against this system.\n\nThe default is validated, ungrouped violations, which is what the agency's page lists apart from its analyte condition. That list is the lower half of the agency's page. Above it, a Group Violations table has one row per group, and this endpoint returns violation records, not groups. GET /v1/water-systems/{pwsid}/summary counts the groups in violations.counts.groups and gives the page's own row count in violations.counts.rowsWithGroupsAsOne. Add grouped=true to include violations rolled into a group. page.total counts whichever reading you asked for, and /summary gives every reading side by side.\n\nThere is no date filter here. For \"has this system had a validated violation since some date\", read violations.lastDeterminedOn: the newest determination among validated violations. It is on /summary and on every GET /v1/water-systems search row, so a search by county answers it for every system in one request.\n\n**Status is not a compliance status.** status=validated means the agency accepted the record as a violation. It does not say whether the violation was fixed, and this API computes no such status; the enforcement actions the agency took are on every row, as takenEnforcementActions. status=rejected returns records the agency did not accept as violations. Never add them to a violation count, however recent they look.\n\nA violation is a regulatory finding, not a measurement, and a monitoring or reporting failure does not by itself mean a contaminant was detected — read category on each row rather than assuming the mix. Do not assume most of a given system's violations are paperwork; read category and count them.","parameters":[{"schema":{"type":"string","enum":["validated","rejected","all"],"default":"validated"},"in":"query","name":"status","required":false,"description":"Whether the agency ACCEPTED the record as a violation — NOT whether it was fixed. \"validated\" (the default) is what the public app shows. \"rejected\" is records the agency did not accept as violations; they are not violations and must not be counted as any. \"all\" mixes the two and is almost never what you want."},{"schema":{"type":"string"},"in":"query","name":"grouped","required":false,"description":"Include violations that are members of a violation group. The agency's page leaves them out of this list and shows them under their group, so the default matches it. Each member is its own row and counts separately in page.total: a group of five adds five, not one. /summary's violations.counts.rowsWithGroupsAsOne.rows is the count with each group once. On or off: send it bare, empty, or =true to turn it on, and =false or leave it out to turn it off. Any other value, a repeated one included, is refused with a 400."},{"schema":{"type":"string"},"in":"query","name":"inGroupOnly","required":false,"description":"Return only violations that BELONG to a violation group — the members behind the Group Violations table on the public page. Implies grouped. This is the request cited by violations.counts.groups.members on /summary; the rows come back with group populated, so you can see which group each belongs to. Note that the public page shows one row per GROUP, not one per member. On or off: send it bare, empty, or =true to turn it on, and =false or leave it out to turn it off. Any other value, a repeated one included, is refused with a 400."},{"schema":{"type":"string"},"in":"query","name":"group","required":false,"description":"One violation GROUP's records, by its printed identity — \"<fiscal year>-<group number>\", as group.id gives it on any violation in that group (for example 2025-47836). Implies grouped, because the default reading excludes grouped violations and a group filter under it would always be empty. An identity that is not two numbers is REFUSED rather than ignored: a filter that silently does nothing answers a different question from the one asked."},{"schema":{"type":"string"},"in":"query","name":"analyteRecorded","required":false,"description":"Return only violations that have an analyte on record. The agency's page applies this condition through a join, so it leaves out violations without one. With the default status and grouped, this reproduces the page's individual Violations table exactly, count included. The page's Group Violations table is counted by violations.counts.groups on /summary. On or off: send it bare, empty, or =true to turn it on, and =false or leave it out to turn it off. Any other value, a repeated one included, is refused with a 400."},{"schema":{"type":"string","enum":["MCL","MON","RPT","PN","TT","SS","V/E"]},"in":"query","name":"category","required":false,"description":"Federal violation category: what kind of failure, not how many records. MCL is a maximum contaminant level or maximum residual disinfectant level; TT, a treatment technique; MON, a monitoring failure; RPT, a reporting failure; PN, a public-notification failure; SS, a sanitary survey not completed by its required date; V/E, a variance or exemption. They are a flat set of federal categories, and nothing here ranks them. /summary's violations.byCategory gives the same counts for one system."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Rows to skip."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string"},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string","description":"Plain-language statement of exactly which records this response counted. Quote it alongside any total taken from page.total."},"agencyCaveat":{"type":"object","description":"The agency's own sentence about these rows, quoted, with where it publishes it. Orthogonal to `reading`: that says what the count counts, this says the rows may no longer be true.","properties":{"text":{"type":"string"},"source":{"type":"string"}},"required":["text","source"]},"notShown":{"type":"object","description":"Present when a wider reading of the same records would return more rows. The default reading reproduces the public application, which shows only violations that have an analyte on record and are not grouped — so this listing can end years before the last thing that actually happened. Check this before telling anyone that nothing has happened lately. Absent when you are already looking at every record.\n\nIt does NOT exclude \"resolved\" violations. Status R is REJECTED, a record the agency did not accept, and no status here says whether a violation was fixed. See the violationStatus code table.","properties":{"count":{"type":"integer","description":"How many further records the widest reading would add."},"why":{"type":"string"},"allValidated":{"type":"string","description":"The request that returns every VALIDATED violation for this system. Widening means dropping the grouping and analyte conditions — it does NOT mean adding status=rejected."},"everyReading":{"type":"string","description":"Every defensible count side by side, each with its query."}},"required":["count","why","allValidated","everyReading"]},"takenEnforcementActionsScope":{"type":"string","description":"What each row's takenEnforcementActions is: every Taken action, in the agency's order, and not a status."},"violations":{"type":"array","items":{"type":"object","properties":{"violationId":{"type":["null","string"],"description":"The agency-assigned violation number. **Unique only within this water system:** the same number is reused on other systems, so it is a key only when paired with the PWSID. Deduplicating or joining on it across systems silently collapses unrelated records."},"fiscalYear":{"type":["null","integer"],"description":"Federal fiscal year the violation was assigned to."},"status":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Compliance status. No authoritative decode exists for this column — labelSource says so. Null when the underlying record has no value for this field."},"violationType":{"type":"object","description":"The specific rule requirement that was not met.","properties":{"code":{"type":["null","string"],"description":"Violation type code. Not unique on its own: the agency's violation type table holds several rows for some codes (a MAJOR and a MINOR under one code), so read it with `name`."},"name":{"type":["null","string"],"description":"The agency's own wording for this violation type, from the row this violation points at."}}},"category":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Federal violation category — whether this is a contaminant-limit, monitoring, reporting, treatment or notification failure. Null when the underlying record has no value for this field."},"analyte":{"type":["null","object"],"description":"Substance the violation concerns, or null when none is recorded.","properties":{"code":{"type":"string","description":"Analyte code."},"name":{"type":["null","string"],"description":"Analyte name."}}},"determinationDate":{"type":["null","string"],"format":"date","description":"When the agency determined the violation. Null when not recorded."},"takenEnforcementActions":{"type":"array","description":"EVERY enforcement action with status Taken that the agency links to this violation, newest first in the agency's own order — name and date, as recorded. The inputs, not a determination: nothing here says whether the violation was resolved. Empty means no Taken action is linked, not that nothing was done. This violation's own record carries the same actions with their numbers and types.","items":{"type":"object","properties":{"name":{"type":["null","string"],"description":"The agency's name for the action type."},"statusDate":{"type":["null","string"],"format":"date","description":"When the action reached status Taken."}},"required":["name","statusDate"]}},"facilityId":{"type":["null","string"],"description":"Facility the violation is attributed to, if any."},"facilityName":{"type":["null","string"],"description":"Name of that facility."},"detail":{"type":"string","description":"The request that returns this one violation in full — with the compliance period, the violation period and the analysis result, which this row does not carry. The agency links every row of its own violations page to the same place."},"group":{"type":["null","object"],"description":"The violation group this record belongs to, or null when it stands alone. A group is the agency's own unit: several violations determined together under one analyte group. Counting the members as separate events overstates how many times something happened. Until 2026-09-03 this API published only a boolean saying a group existed.","properties":{"id":{"type":["null","string"],"description":"Fiscal year and group number, as the agency writes it, e.g. 2025-47836. Null when either half is missing — never a composed string containing \"null\"."},"analyteGroup":{"type":["null","object"],"description":"The analyte group the violations were determined under — the agency's own code and wording, e.g. DBP / TTHM/HAA5. Not a decoded code: name comes from the agency's table, so there is no labelSource on it.","properties":{"code":{"type":["null","string"],"description":"Analyte group code."},"name":{"type":["null","string"],"description":"The agency's own wording for this analyte group."},"members":{"type":"string","description":"The analytes this group stands for. A schedule or violation filed under a group covers its members; nothing else on this payload expands it, and no analyteType filter reproduces it."}}}},"required":["id","analyteGroup"]},"isGrouped":{"type":"boolean","description":"Whether this violation belongs to a violation group. Grouped violations are excluded by default."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["violationId","status","isGrouped","dataQuality"]}}},"required":["meta","page","pwsid","reading","violations"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/violations/{violationId}":{"get":{"summary":"One violation","tags":["water systems"],"description":"One violation in full, including the compliance period, the violation period and the analysis result — the three the agency prints on its own violation page and omits from its violations list.\n\nThe field names follow the agency's own labels for these fields rather than the column names behind them, which do not agree. The labels are the ones the agency prints on its own violation page.\n\nanalysisResult is null unless the agency recorded a value, which is the usual case: only an exceedance has a number that caused it. The enforcement actions the agency lists beneath the violation on its own page ARE served, in enforcementActions — read its `scope`, which says which of them the agency publishes and which it withholds.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true},{"schema":{"type":"string"},"in":"path","name":"violationId","required":true,"description":"The agency-assigned violation number, exactly as published in `violationId` by GET /v1/water-systems/{pwsid}/violations. Digits only. It is unique within one water system and no further, so it is only meaningful under a PWSID."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"pwsid":{"type":"string","description":"The system this violation belongs to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"enforcementActions":{"type":"object","description":"The second table the agency prints on its own violation page. Only actions whose status is Taken, which is the agency's own filter — read `scope` before drawing anything from an empty list.","properties":{"count":{"type":"integer","description":"How many actions this listing holds."},"scope":{"type":"string","description":"The per-request statement of what this listing is of. **Read the value before concluding anything from count: 0.** It is the only place the narrowing applied to this request is stated. This object's own description says what the narrowing normally is."},"actions":{"type":"array","items":{"type":"object","properties":{"id":{"type":["null","string"],"description":"The agency's enforcement action number: federal fiscal year and a unique number, joined as its own page joins them. Null when either half is missing — never a string containing \"null\"."},"idDefinition":{"type":"string"},"idDefinitionSource":{"type":"string","description":"Where that sentence was quoted from."},"actionType":{"type":"object","description":"Three coded columns the agency's page welds into one cell. Published apart as well as joined, because a consumer comparing them needs them apart.","properties":{"location":{"type":["null","string"],"description":"Who took the action — see definition."},"formalType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The second part of the action type code. Nothing decodes it on its own, so the label is null; the action's `name` is the agency's wording for the whole type. Null when the underlying record has no value for this field."},"subCategory":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The third part of the action type code. Nothing decodes it on its own, so the label is null; the action's `name` is the agency's wording for the whole type. Null when the underlying record has no value for this field."},"displayedAs":{"type":"string","description":"The three joined the way the agency's page joins them, which is without a separator."},"definition":{"type":"string"},"definitionSource":{"type":"string"}},"required":["displayedAs","definition","definitionSource"]},"name":{"type":["null","string"],"description":"The agency's own description of the action."},"nameDefinition":{"type":"string"},"nameDefinitionSource":{"type":"string","description":"Where that sentence was quoted from."},"status":{"type":["null","object"],"description":"Potential, Taken or Rejected. This listing carries only Taken, so the field is published for completeness rather than to be branched on.","properties":{"code":{"type":"string"},"label":{"type":["null","string"]},"labelSource":{"type":"string"}},"required":["code","label","labelSource"]},"date":{"type":["null","string"],"format":"date","description":"Date of the action's latest status. Null when not recorded."},"dateDefinition":{"type":"string"},"dateDefinitionSource":{"type":"string","description":"Where that sentence was quoted from."}},"required":["id","actionType","name","status","date"]}}},"required":["count","scope","actions"]},"violation":{"type":"object","properties":{"violationId":{"type":["null","string"],"description":"The agency-assigned violation number. **Unique only within this water system:** the same number is reused on other systems, so it is a key only when paired with the PWSID. Deduplicating or joining on it across systems silently collapses unrelated records."},"fiscalYear":{"type":["null","integer"],"description":"Federal fiscal year the violation was assigned to."},"status":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Compliance status. No authoritative decode exists for this column — labelSource says so. Null when the underlying record has no value for this field."},"determinationDate":{"type":["null","string"],"format":"date","description":"When the agency determined the violation. Null when not recorded."},"violationType":{"type":"object","description":"The specific rule requirement that was not met.","properties":{"code":{"type":["null","string"],"description":"Violation type code. Not unique on its own: the agency's violation type table holds several rows for some codes (a MAJOR and a MINOR under one code), so read it with `name`."},"name":{"type":["null","string"],"description":"The agency's own wording for this violation type, from the row this violation points at."}}},"category":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Federal violation category — whether this is a contaminant-limit, monitoring, reporting, treatment or notification failure. Null when the underlying record has no value for this field."},"analyte":{"type":["null","object"],"description":"Substance the violation concerns, or null when none is recorded.","properties":{"code":{"type":"string","description":"Analyte code."},"name":{"type":["null","string"],"description":"Analyte name."}}},"facilityId":{"type":["null","string"],"description":"Facility the violation is attributed to, if any."},"facilityName":{"type":["null","string"],"description":"Name of that facility."},"isGrouped":{"type":"boolean","description":"Whether this violation belongs to a violation group."},"group":{"type":["null","object"],"description":"The violation group this record belongs to, or null when it stands alone. A group is the agency's own unit: several violations determined together under one analyte group.","properties":{"id":{"type":["null","string"],"description":"Fiscal year and group number, as the agency writes it. Null when either half is missing."},"memberCount":{"type":["null","integer"],"description":"How many violation records this group holds, this one included — so a reader is told how much of the record they are NOT looking at. Null when this violation is not in a group."},"members":{"type":["null","string"],"description":"The request that returns this group's records, this one included. Null when this violation is not in a group."},"analyteGroup":{"type":["null","object"],"description":"The analyte group the violations were determined under.","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"members":{"type":"string","description":"The analytes this group stands for. A schedule or violation filed under a group covers its members; nothing else on this payload expands it, and no analyteType filter reproduces it."}}}}},"compliancePeriod":{"type":"object","description":"The agency's \"Compliance Period\". Generally the monitoring period in which samples were not taken, or were taken and exceeded a limit.","properties":{"begins":{"type":["null","string"],"description":"First date of the period, as the agency records it."},"ends":{"type":["null","string"],"description":"Last date of the period. Null where the agency has not recorded one."},"definition":{"type":"string","description":"The application's own definition of this field, quoted."},"definitionSource":{"type":"string","description":"Where the definition came from. \"dww-help\" is the help system shipped inside Drinking Water Watch — published text, and the application's wording rather than the agency's."}},"required":["begins","ends","definition","definitionSource"]},"violationPeriod":{"type":"object","description":"The agency's \"Violation Period\". Generally used for a one-time requirement such as installing treatment or submitting a report.","properties":{"begins":{"type":["null","string"],"description":"First date of the period, as the agency records it."},"ends":{"type":["null","string"],"description":"Last date of the period. Null where the agency has not recorded one."},"definition":{"type":"string","description":"The application's own definition of this field, quoted."},"definitionSource":{"type":"string"}},"required":["begins","ends","definition","definitionSource"]},"analysisResult":{"type":["null","object"],"description":"The value that caused the exceedance, where the agency recorded one. Null where it did not, which is the ordinary case and is NOT a statement that no exceedance occurred.","properties":{"value":{"type":["null","string"],"description":"The measure as the agency stores it — CHARACTER data, published verbatim and NOT converted to a number. Do not assume it parses."},"unit":{"type":["null","string"],"description":"Unit of measure, as recorded."},"displayedAs":{"type":"string","description":"Value and unit joined the way the agency's own page joins them, which is without a separator. Published so anyone reproducing that page does not have to guess."},"definition":{"type":"string"},"definitionSource":{"type":"string"}},"required":["value","unit","displayedAs","definition","definitionSource"]},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["violationId","status","compliancePeriod","violationPeriod","analysisResult","dataQuality"]},"allViolations":{"type":"string","description":"The request that returns every violation for this system."}},"required":["meta","pwsid","reading","violation","enforcementActions","allViolations"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/facilities/{facilityId}/mdbp-summaries":{"get":{"summary":"MDBP summaries for one facility","tags":["water systems"],"description":"Summarized field sample results for one facility. The help shipped with the agency's application describes them as \"summaries of field results collected and assessed for the federal Interim Enhanced Surface Water Treatment Rule and Disinfectant ByProduct Rule. They are also called MDBP Summaries.\" The agency's pages reach them from the facility page.\n\nEach summary belongs to a designated facility analyte level, and the level it was assessed against travels with it in `assessedAgainst` — a facility can have several levels for one analyte over time.\n\n`monitoringCompliance` is not a yes/no field. The agency records three values and one of them means a minor violation, so read the code; testing for \"Y\" reads a No-Major Violation as a pass.\n\nNo date default is applied here and the agency's own page applies one — read `legacyDefault`.","parameters":[{"schema":{"type":"string"},"in":"query","name":"periodFrom","required":false,"description":"Only monitoring periods beginning on or after this date (YYYY-MM-DD)."},{"schema":{"type":"string"},"in":"query","name":"periodTo","required":false,"description":"Only monitoring periods beginning on or before this date (YYYY-MM-DD)."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page, up to 500."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Rows to skip."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true},{"schema":{"type":"string"},"in":"path","name":"facilityId","required":true,"description":"The facility's state-assigned identifier, as published by GET /water-systems/{pwsid}/facilities. PERCENT-ENCODE IT: many contain a space."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string"},"facilityId":{"type":"string","description":"The facility these summaries belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"legacyDefault":{"type":"object","description":"The date default the agency's own page applies and this endpoint does not, with the request that reproduces it.","properties":{"note":{"type":"string"},"reproduceIt":{"type":"string"}},"required":["note","reproduceIt"]},"window":{"type":"object","description":"The date bounds applied to this listing, on the monitoring-period start date. Present only when bounds are in force. `source` is present only when this API chose them; its absence means they are the ones you passed. See `notShown` for how many records they excluded.","properties":{"periodFrom":{"type":"string","format":"date"},"periodTo":{"type":"string","format":"date"},"source":{"type":"string"}}},"notShown":{"type":"object","description":"How many summaries the date window you asked for excluded. Absent when no window was applied or nothing was hidden.","properties":{"count":{"type":"integer"},"why":{"type":"string"},"everything":{"type":"string","description":"The request that returns every summary for this facility."}},"required":["count","why","everything"]},"summaries":{"type":"array","items":{"type":"object","properties":{"analyte":{"type":["null","object"],"properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}}},"summaryType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"What was measured and generally where — turbidity at the combined filter effluent, an entry-point residual disinfectant concentration, and so on. Null where the agency recorded none. Null when the underlying record has no value for this field.","type":["object","null"]},"monitoringPeriod":{"type":"object","properties":{"begins":{"type":["null","string"],"format":"date"},"ends":{"type":["null","string"],"format":"date"}},"required":["begins","ends"]},"samplesExceeded":{"type":["null","integer"],"description":"How many samples fell beyond or outside the level set in the facility analyte level for this analyte. Where the level is a MINIMUM this counts samples below it, so \"exceeded\" is the agency's word for either direction."},"percentExceeded":{"type":["null","string"],"description":"The same as a percentage, as the agency records it."},"periodAverage":{"type":"object","description":"The monitoring period average, as recorded. A number, from a NUMERIC column: 0 is a recorded value, null is not recorded.","properties":{"value":{"type":["null","number"]},"unit":{"type":["null","string"]}},"required":["value","unit"]},"runningAnnualAverage":{"type":"object","description":"The running annual average, as recorded. A number, from a NUMERIC column: 0 is a recorded value, null is not recorded.","properties":{"value":{"type":["null","number"]},"unit":{"type":["null","string"]}},"required":["value","unit"]},"monitoringCompliance":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the monitoring and reporting requirement was met, as the agency records it. THREE values, not two: one of them means a minor violation. Read the code. Null when the underlying record has no value for this field.","type":["object","null"]},"levelCompliance":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the designated level was complied with, as the agency records it. Null when the underlying record has no value for this field.","type":["object","null"]},"assessedAgainst":{"type":"object","description":"The facility analyte level this summary was measured against — the same record the facility detail publishes under analyteLevels.","properties":{"controlLevelType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether that level is a maximum, minimum, average or 95th percentile. Null when the underlying record has no value for this field."},"measure":{"type":["null","string"],"description":"CHARACTER data, published verbatim."},"unit":{"type":["null","string"]}},"required":["controlLevelType","measure","unit"]}},"required":["analyte","summaryType","monitoringPeriod","samplesExceeded","periodAverage","runningAnnualAverage","monitoringCompliance","levelCompliance","assessedAgainst"]}}},"required":["meta","page","pwsid","facilityId","reading","legacyDefault","summaries"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/lead-and-copper":{"get":{"summary":"Lead and copper 90th-percentile summaries","tags":["water systems"],"description":"The lead and copper 90th-percentile sample summaries the agency assessed for this system, one row per analyte per monitoring period.\n\nWhat the 90th percentile is, in the agency's own words: \"The 90th percentile of all samples collected from the distribution system is the standard that the US Environmental Protection Agency has chosen for determining compliance with the Lead and Copper Action Level under the federal Lead and Copper Rule. The 90th percentile is determined by ranking all sample results from lowest to highest and finding a result that is 90% of the number of sample results from the top.\" It is not an average, and it is not the highest result.\n\n**This endpoint does not say whether a system exceeded the action level.** It publishes the measure the agency recorded, as the agency's page does. Whether the agency treated a result as an exceedence is a separate determination, recorded in two places, both served here:\n  - GET /v1/water-systems/{pwsid}/milestones. Milestone types PB90 and CU90 are the finding: the agency words each as \"90th percentile action level exceedence\", so the type is the determination and the figure is its size. Read the type, not the number.\n  - GET /v1/water-systems/{pwsid}/violations, where such a determination became a violation the agency is enforcing.\nAn empty result from either means the agency holds no such record; it does not say nothing was exceeded. This API publishes no numeric action level, and you should not supply one. The agency's application defines the term and the method and publishes no threshold figure, so a comparison against a number you brought is yours, not the agency's.\n\nA measure of 0 is a recorded measurement, not a missing one — 3,220 of the 34,557 summaries on file are zero. The result at the 90th-percentile rank was zero; that does not say nothing was detected in the period.\n\nBy default this returns every monitoring period on record. The agency's page defaults to the last two years; `legacyDefault` in the response explains why that is not reproduced and how to reproduce it.","parameters":[{"schema":{"type":"string"},"in":"query","name":"periodFrom","required":false,"description":"Only monitoring periods beginning on or after this date (yyyy-mm-dd). This is the period begin date, which the agency's own form filters on, not the sample or receipt date."},{"schema":{"type":"string"},"in":"query","name":"periodTo","required":false,"description":"Only monitoring periods beginning on or before this date (yyyy-mm-dd)."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Rows to skip."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these summaries belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string","description":"Which set of summaries this response counted, in a sentence."},"legacyDefault":{"type":"object","description":"How this endpoint differs from the agency's page, and the request that makes them agree. The difference is a default, not a boundary: no fact is added or withheld either way.","properties":{"note":{"type":"string"},"reproduceIt":{"type":"string","description":"The request that reproduces the agency page's default two-year window."}},"required":["note","reproduceIt"]},"window":{"type":"object","description":"The date bounds applied to this listing, on the monitoring-period start date. Present only when bounds are in force. `source` is present only when this API chose them; its absence means they are the ones you passed. See `notShown` for how many records they excluded.","properties":{"periodFrom":{"type":"string","format":"date"},"periodTo":{"type":"string","format":"date"},"source":{"type":"string"}}},"notShown":{"type":"object","description":"Present only when a date window hides summaries that exist.","properties":{"count":{"type":"integer"},"why":{"type":"string"},"everything":{"type":"string","description":"The request that returns every summary on record."}},"required":["count","why","everything"]},"findings":{"type":"object","description":"The whole set this request matched, across every page. Absent when nothing matched, because a span of nulls would read as \"no dates recorded\" rather than \"no rows\".","properties":{"scope":{"type":"string","description":"Which set the other figures in this object are over, in a sentence."},"monitoringPeriod":{"type":"object","description":"How far this system's lead and copper record reaches: the earliest period start and the latest period end across every matching row. It is called monitoringPeriod, as each row calls it, not `collected` as the sample collections call theirs, because a monitoring period is not a collection date. Rows are sorted by start date only, so the latest end is not necessarily on the last row.","properties":{"earliest":{"type":["null","string"],"format":"date"},"latest":{"type":["null","string"],"format":"date"}},"required":["earliest","latest"]}},"required":["scope","monitoringPeriod"]},"summaries":{"type":"array","items":{"type":"object","description":"One analyte's 90th-percentile summary for one monitoring period.","properties":{"substance":{"type":["null","string"],"description":"Lead or Copper — the word the agency's own page prints for this row, which its scriptlet derives from the analyte code. Published beside the code rather than instead of it."},"analyte":{"type":["null","object"],"description":"The analyte as the agency's own table records it. PB90 and CU90 are the 90th-percentile summary analytes, not the underlying lead and copper analytes.","properties":{"code":{"type":"string"},"name":{"type":["null","string"],"description":"The agency's own wording, e.g. LEAD SUMMARY."}},"required":["code","name"]},"monitoringPeriod":{"type":"object","description":"The period the summary covers. Lead and copper monitoring periods are commonly three years long, so a gap between periods is normal rather than a lapse.","properties":{"begins":{"type":["null","string"],"description":"First day of the monitoring period (ISO 8601)."},"ends":{"type":["null","string"],"description":"Last day of the monitoring period (ISO 8601)."}},"required":["begins","ends"]},"ninetiethPercentile":{"type":"object","description":"The 90th-percentile result and the unit it is in, {value, unit} like every other measured value here. Zero is a recorded measurement — 3,220 of the 34,557 summaries on file are zero: the result at the 90th-percentile rank was zero, and the agency prints it. Do not treat it as missing. **Do not read zero as \"nothing was detected\":** this is one ranked result, and a system with a zero here can hold detections in the same period; see the samples for that analyte. Do not compare it to an action level and report the answer as the agency's: the agency records its own determinations as violations.","properties":{"value":{"type":["null","number"]},"unit":{"type":["null","string"],"description":"Read from the lead and copper summary record rather than assumed. Expect MG/L throughout in practice — but it is not a constant, and if the agency ever loads a summary in different units you will get those units instead of a silent thousandfold error. The agency's own page hardcodes \"Measure (mg/l)\" in its column header and selects no unit; this publishes the same fact from the better source."}},"required":["value","unit"]},"sampleCount":{"type":["null","integer"],"description":"How many samples the 90th percentile was computed from. Small counts make the percentile coarse — with 10 samples it is the 9th-highest result."},"resultQuality":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The quality status of the result. Only Accepted and Validated appear here, because the agency's own page lists only those two — Preliminary and Rejected results exist in the records and are not shown by either of us. Null when the underlying record has no value for this field."},"facilityId":{"type":["null","string"],"description":"The facility the summary is attributed to. Lead and copper tap samples are collected from the distribution system by definition, so expect the same distribution-system facility on every row: it is unlikely to distinguish anything between systems, so check before mining it. The agency's page blanks the cell when it is null. Resolve it with GET /v1/water-systems/{pwsid}/facilities/{facilityId}."},"summaryReceivedDate":{"type":["null","string"],"description":"When the agency received the summary (ISO 8601). This is a column agencies commonly leave unpopulated, and where it is, the agency's own page shows an empty cell on every row. A null therefore carries NO information about this system: it does not mean the agency has a gap in this town's records, it means the field is not tracked. Do not report it as a records gap."}},"required":["substance","analyte","monitoringPeriod","ninetiethPercentile","sampleCount","resultQuality","facilityId","summaryReceivedDate"]}}},"required":["meta","page","pwsid","reading","legacyDefault","summaries"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/samples":{"get":{"summary":"Chem/rad samples and their results","tags":["water systems"],"description":"Water samples analysed for chemicals, radionuclides and water-quality parameters, each with every result on it inline. This is the agency's Chem/Rad Samples page and the Chem/Rad Sample Results page behind it, as one record.\n\n**To answer \"has anything been detected\", read `findings` and do not page.** `findings` describes the whole record this request matched, across every page, and does not change with `limit`. It carries how many distinct analytes were ever detected, the detected, not-detected and not-reported split over every result, the date span of the record, and `detectedAnalytes`. The rows in `samples` are one page, newest first. Page one leaves out analytes a system stopped detecting years ago, so counting detections in it will understate them.\n\nRead `resultType` before reading any number. A result is one of three things:\n  detected      — a concentration was measured. concentration holds it.\n  not-detected  — the laboratory found less than its reporting level. concentration is null and reportingLevel says what the result was below.\n  not-reported  — nothing was recorded for this analyte on this sample.\n\nNon-detects are usually most of what this endpoint publishes, and they carry a number: the laboratory's reporting level. The help topic says a flagged result \"is considered a non-detect\". Reading that number as a concentration reports a detection of something that was not detected.\n\nNot-detected and not-reported are different facts. \"We tested and found nothing\" and \"no result was recorded\" say different things, and only the first says a test happened.\n\nNever infer a unit. The unit belongs to each result, not to the analyte, and some analytes are reported in more than one (TTHM and HAA5 appear in both MG/L and UG/L). Where no unit was recorded the field is null and `dataQuality` says so.\n\nBy default this returns every sample on record. The agency's page defaults to the last two years; `legacyDefault` in the response has the request that reproduces it.\n\nTotal coliform samples are not here. They are a separate record type, and this endpoint leaves them out as the agency's page does.\n\nThis endpoint does not say whether a limit was exceeded. It publishes what the laboratory reported. Whether the agency treated a result as a violation is a separate record: GET /v1/water-systems/{pwsid}/violations.","parameters":[{"schema":{"type":"string"},"in":"query","name":"analyte","required":false,"description":"Only samples analysed for this analyte code. **Those samples arrive with every result on them, including results for other analytes.** The filter selects samples; it does not prune results. One bottle is tested for many substances at once, so the whole sample comes back and `results[].analyte.code` says which result is which.\n\nCheck `results[].analyte.code` before aggregating. A consumer that read `samples[].results[]` as \"the results for the analyte I asked for\" computed a chloroform maximum from a TTHM value, and the wrong figure looked plausible.\n\nGET /v1/water-systems/{pwsid}/analytes lists the codes this system has, and links each one here. Three coliform codes (3100, 3013, 3014) return nothing, which is the agency's own behaviour and is declared on that endpoint."},{"schema":{"type":"string"},"in":"query","name":"collectedFrom","required":false,"description":"Only samples collected on or after this date (yyyy-mm-dd)."},{"schema":{"type":"string"},"in":"query","name":"collectedTo","required":false,"description":"Only samples collected on or before this date (yyyy-mm-dd)."},{"schema":{"type":"string"},"in":"query","name":"labSampleNumber","required":false,"description":"Only samples carrying this laboratory number. NOT a key: it is the laboratory's own number and a handful are reused across samples, so this can match more than one."},{"schema":{"type":"string"},"in":"query","name":"collectedOn","required":false,"description":"Only samples whose collection ENDED on this date (yyyy-mm-dd). Narrower than collectedFrom/collectedTo, which bound a range."},{"schema":{"type":"string"},"in":"query","name":"samplingPoint","required":false,"description":"Only samples taken at this sampling point, by its identifier."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"SAMPLES per page, not results. Each sample carries every result on it, up to 37."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Samples to skip."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string"},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string","description":"Which set of samples this response counted, in a sentence."},"legacyDefault":{"type":"object","description":"How this endpoint differs from the agency's page, and the request that makes them agree. A default, not a boundary — no fact is added or withheld.\n\nABSENT WHEN analyte IS SET, because there is no divergence to report: the agency applies its two-year default only on the main Chem/Rad Samples list, not on the by-analyte page its Analyte List links to.","properties":{"note":{"type":"string"},"reproduceIt":{"type":"string"}},"required":["note","reproduceIt"]},"window":{"type":"object","description":"The date bounds applied to this listing, on the collection date. Present only when bounds are in force. `source` is present only when this API chose them; its absence means they are the ones you passed. See `notShown` for how many records they excluded.","properties":{"collectedFrom":{"type":"string","format":"date"},"collectedTo":{"type":"string","format":"date"},"source":{"type":"string"}}},"notShown":{"type":"object","description":"Present only when a date window hides samples that exist.","properties":{"count":{"type":"integer"},"why":{"type":"string"},"everything":{"type":"string"}},"required":["count","why","everything"]},"findings":{"type":"object","description":"**The whole record this request matches, across every page.** Read it before you read a row, and lead with it if you are writing for a person.\n\nA page is not a sample of the record. Samples arrive newest first, so the analytes a system used to find and no longer does are the ones page one cannot show. Most systems with any detection have a detected analyte that page one cannot show. Which analytes fall on page one depends only on date order, not on how serious they are. Read `analytes.total` and the /analytes endpoint before describing what a system has ever been found to contain.\n\nIt covers the same query as the rows: the analyte filter and the date window both apply, and `note` says which in words.\n\nIt compares nothing to a limit. This record type publishes no maximum contaminant level, standard or health threshold; the agency removed the MCL column from its own pages in 2008. A detection here is a measurement the agency recorded, not a finding that anything was wrong. Do not supply a threshold from elsewhere and present the comparison as this record.","properties":{"violations":{"type":"object","description":"Present only when this block reports 0 detected over the whole record (no date window, sampling point, lab number or collection date) and the agency holds validated violations recorded against analyte codes this block covers. Absent otherwise. Which block covers a contaminant is the analyte's own type code: MOR for the microbiological block, any other for chem/rad, and RL (a rule, not an analyte a sample is tested for) for neither. With analyte=, the codes narrow to that analyte exactly as this block does. count is reproducible from `all` by analyte.code. It says nothing about what those violations mean.","properties":{"count":{"type":"integer","description":"Validated violations recorded against the analyte codes in analyteCodes, grouped records included."},"analyteCodes":{"type":"array","items":{"type":"string"},"description":"The agency's analyte codes those violations are recorded against. Rule codes (type RL) are never among them."},"all":{"type":"string","description":"The list that reproduces count: every validated violation, grouped included."},"note":{"type":"string"}},"required":["count","analyteCodes","all","note"]},"analytes":{"type":"object","description":"How many distinct analytes the query matched, and how many of those have ever returned a detection. everDetected is the length of detectedAnalytes.","properties":{"total":{"type":"integer"},"everDetected":{"type":"integer"}},"required":["total","everDetected"]},"results":{"type":"object","description":"Every result the query matches, in the three states resultType uses, plus the total. A result is not-detected when the lab set the less-than flag OR wrote a value beginning \"<\" — either signal alone is sufficient, because the error to avoid is reporting a detection where the lab reported none.","properties":{"detected":{"type":"integer"},"notDetected":{"type":"integer"},"notReported":{"type":"integer"},"total":{"type":"integer"}},"required":["detected","notDetected","notReported","total"]},"samples":{"type":"object","description":"Samples, not results — the same figure as page.total.","properties":{"total":{"type":"integer"}},"required":["total"]},"collected":{"type":"object","description":"The date range of the whole matched record, not of this response. Samples are returned newest first, so the earliest date is on the last page; this is that date, computed. Both are null when nothing matched. **It is the date the agency's record begins, not the date this system was first tested.** An earlier test that was never recorded here would look the same.","properties":{"earliest":{"type":["null","string"],"format":"date"},"latest":{"type":["null","string"],"format":"date"}},"required":["earliest","latest"]},"unitsVary":{"type":"object","description":"Present only when some analyte in this record was reported in more than one unit. **Read it before you aggregate anything.** A maximum taken per analyte across mixed units can be off by a factor of 1000, and nothing in the numbers shows it.","properties":{"analytes":{"type":"array","items":{"type":"string"},"description":"The analyte codes affected, by code as published on each result."},"why":{"type":"string"},"readOnEachResult":{"type":"string","description":"The field on every individual result that carries its unit."}},"required":["analytes","why","readOnEachResult"]},"detectedAnalytes":{"type":"array","description":"One entry per analyte with at least one detected result, most detections first. firstDetectedOn and lastDetectedOn are collection dates, so \"arsenic, last detected 2014\" is answerable without paging. An analyte absent from this list was either never detected or never tested for, and those are different: /water-systems/{pwsid}/analytes lists what has been tested for.","items":{"type":"object","properties":{"analyte":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Inorganic, organic, radiological or physical. Decoded here for every detected analyte, including those on no page you have fetched. Null when the underlying record has no value for this field."}},"required":["code","name","type"]},"unitsReported":{"type":"integer","description":"How many different units this analyte was reported in across the records this request matched. Greater than 1 means results for this analyte are not directly comparable: MG/L and UG/L differ by a factor of 1000 and both occur. 0 means no result carried a unit at all. See findings.unitsVary."},"results":{"type":"object","properties":{"total":{"type":"integer"},"detected":{"type":"integer"}},"required":["total","detected"]},"firstDetectedOn":{"type":["null","string"]},"lastDetectedOn":{"type":["null","string"]}},"required":["analyte","results","firstDetectedOn","lastDetectedOn"]}},"scope":{"type":"string","description":"Which set these numbers are over, in a sentence. **Read it first when you have filtered by analyte.** The analyte filter selects samples, and the agency's results page shows every analyte on a selected sample. So this block reports every analyte found on those samples, not only that analyte's results, and a query for one analyte can report many detected analytes."},"note":{"type":"string"}},"required":["analytes","results","samples","collected","detectedAnalytes","scope","note"]},"alsoRecorded":{"type":"object","description":"The other record types this system's testing is kept in. The dates in `findings` cover this collection only, and a system's earliest test may be in another one. Each entry names the collection and the field to read there: the two sample collections publish `findings.collected`, and the summaries publish `findings.monitoringPeriod`, which is a monitoring period, not a collection date. The figures are not copied here; read them from each collection.","properties":{"note":{"type":"string"},"collections":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string"},"holds":{"type":"string","description":"What kind of record that collection holds."},"read":{"type":"string","description":"The field there that says how far its record reaches."},"url":{"type":"string"}},"required":["resource","holds","read","url"]}}},"required":["note","collections"]},"samples":{"type":"array","items":{"type":"object","description":"One sample, with every result recorded against it.","properties":{"labSampleNumber":{"type":["null","string"],"description":"The number the laboratory assigned, as the agency prints it. **It is not an identifier:** these repeat within a single water system, and some samples carry none at all. There is no per-sample URL for that reason."},"sampleType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Why the sample was taken. Check this before quoting a result as a measurement of the water: blanks, duplicates and performance-evaluation samples are laboratory quality control. Every sample type that reaches this endpoint has a label from the help topic. Null when the underlying record has no value for this field."},"collected":{"type":"object","properties":{"date":{"type":["null","string"],"description":"Collection end date (ISO 8601)."},"time":{"type":["null","string"],"description":"Collection end time as HH:MM:SS, when recorded. Seconds are included because the agency prints them and some samples carry a non-zero one."}},"required":["date","time"]},"samplingPoint":{"type":"object","description":"Where the sample was taken. The identifier is the agency's own sampling point code; the location is its description of it.","properties":{"id":{"type":["null","string"]},"location":{"type":["null","string"],"description":"**The location may be withheld.** Where a point identifies a private dwelling this API publishes null and says so in dataQuality on the same object -- our decision, not an absence in the agency's records. The id, the type and every result taken there are unaffected."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["id","location"]},"laboratory":{"type":["null","string"],"description":"The accredited laboratory that ran the analysis."},"findings":{"type":"object","description":"Counts of the results in `results`, by the same three states. It is not a yes-or-no \"anything detected?\", because false would merge \"nothing was detected\" with \"nothing was reported\". `total` is the length of `results`, which is never truncated.","properties":{"detected":{"type":"integer"},"notDetected":{"type":"integer"},"notReported":{"type":"integer"},"total":{"type":"integer"}},"required":["detected","notDetected","notReported","total"]},"results":{"type":"array","description":"Every result on this sample. Complete and never paged — a sample carries at most a few dozen results.","items":{"type":"object","properties":{"analyte":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"],"description":"The agency's own name for the substance."},"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"What kind of substance this is — inorganic chemical, organic chemical, radionuclide, water quality, and so on. Null when the underlying record has no value for this field."}},"required":["code","name","type"]},"methodCode":{"type":["null","string"],"description":"The analytical method the laboratory used. No code table exists for it here, so it is published undecoded, as the agency prints it."},"resultKind":{"type":"string","enum":["concentration","presence-absence"],"description":"Which kind of result this is, and therefore which fields it has. Always \"concentration\" on this endpoint. It is published rather than implied because /coliform-samples serves \"presence-absence\" results, which share resultType with these and carry no concentration field at all. Branch on this rather than on whether a key is missing. The full vocabulary is at /coverage."},"resultType":{"type":"string","enum":["detected","not-detected","not-reported"],"description":"**Read this first.** detected: a concentration was measured, and concentration holds it. not-detected: the laboratory reported less than its reporting level — concentration is null and reportingLevel says what the result was below. not-reported: nothing was recorded."},"concentration":{"type":["null","object"],"description":"Present only when resultType is \"detected\". Null otherwise, and a null here never means zero.","properties":{"value":{"type":["null","number"],"description":"Null when the agency recorded the result as text that is not a number; reported.text has it verbatim."},"unit":{"type":["null","string"],"description":"As recorded, and never inferred from the analyte: some analytes are reported in more than one unit."}},"required":["value","unit"]},"reportingLevel":{"type":["null","object"],"description":"The threshold the laboratory reported against — the agency's own column heading is \"Reporting Level\". Its type says which kind: a Minimum Reporting Level is generally higher than a Method Detection Limit, so \"below the MRL\" is the weaker statement. Null only when the agency recorded neither a limit nor a limit type.","properties":{"value":{"type":["null","number"],"description":"Null when the agency recorded a limit type but stored 0 or a negative as the limit itself, which is not a level. dataQuality says so, and it is nearly always an MRL. The result is still a non-detect; what it was below is not on record."},"unit":{"type":["null","string"]},"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Which limit this is: MDL (Method Detection Limit) or MRL (Minimum Reporting Level). They are not interchangeable, and this is published INDEPENDENTLY of the value — the agency renders them as two columns and records them separately. Null when the underlying record has no value for this field."}},"required":["value","unit","type"]},"reported":{"type":"object","description":"What the agency stored, verbatim, in every branch. The column is text, not a number: a handful carry a trailing dot (\"100.\") and a few hold a value with a \"<\" in it, such as \"<0.0005\". Parse it defensively rather than assuming it is numeric.","properties":{"text":{"type":["null","string"]},"unit":{"type":["null","string"]}},"required":["text","unit"]},"monitoringPeriod":{"type":"object","description":"The compliance period this result counts toward, where the agency linked one.","properties":{"begins":{"type":["null","string"]},"ends":{"type":["null","string"]}},"required":["begins","ends"]},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["analyte","methodCode","resultKind","resultType","concentration","reportingLevel","reported","monitoringPeriod","dataQuality"]}}},"required":["labSampleNumber","sampleType","collected","samplingPoint","laboratory","findings","results"]}}},"required":["meta","page","pwsid","reading","findings","samples"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/analytes":{"get":{"summary":"Analytes this system has been sampled for","tags":["water systems"],"description":"Every substance this water system has a recorded chem/rad result for, with a link to the samples behind each. This is the agency's own Analyte List page — a second way into the records at GET /v1/water-systems/{pwsid}/samples, not a different set of facts.\n\nUse it to answer \"has this system ever been tested for X\" in one request instead of paging the samples.\n\nThree analytes have no samples door. The agency's own sample list excludes coliform codes 3100, 3013 and 3014 by code while this list includes them, so its links for those rows open an empty page. Here their samples field is null and why says so. Their results are counted in resultCount and are published through the Total Coliform Rule records.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string"},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"analytes":{"type":"array","items":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"What kind of substance this is. Null when the underlying record has no value for this field."},"results":{"type":"object","description":"How many results this system has for this analyte, and what that number is a count OF. It is NOT the number of rows the records link returns: that link lists SAMPLES, and one sample carries up to 37 results. COLIFORM (TCR) is where the difference bites: a system can have many thousands of results here against a samples door that returns nothing at all.","properties":{"count":{"type":["null","integer"],"description":"A count of results, not of detections. Most results in this data are non-detects or were not reported at all; read resultType on the samples for what was actually found."},"basis":{"type":"string","description":"Which set the count is over. Named rather than assumed, so a second basis cannot arrive silently under the same key."},"lastCollectedOn":{"type":["null","string"],"description":"Collection date of the most recent sample carrying this analyte."}},"required":["count","basis","lastCollectedOn"]},"records":{"type":["null","object"],"description":"Where this analyte's results live — one destination, carrying its own kind. Follow href and label the link with label; branch on kind only if you need to. Deliberately ONE field rather than one URL per record type, and this is the endpoint that proved it worth doing: the three coliform codes published null here until the coliform endpoints shipped and now carry kind \"tcr\", which every consumer reading href followed with no change. A second sibling key would have left a consumer that selects this one rendering a dead end forever.\n\nNull only when the analyte has no code to link on.","properties":{"href":{"type":"string"},"kind":{"type":"string","enum":["samples","tcr"],"description":"The kind of record behind href, and therefore its shape. \"samples\" is chem/rad, with a concentration per result; \"tcr\" is coliform and microbial, presence-or-absence with no concentration at all. The full vocabulary, with what each kind means, is at /coverage."},"label":{"type":"string","description":"What the link leads to, in a phrase fit to print."}},"required":["href","kind","label"]},"agencyNote":{"type":"string","description":"What the AGENCY does with this analyte, when that is worth saying. Present whenever its own sample list excludes the analyte by code — independently of whether records points somewhere, and it will REMAIN once those records are served elsewhere. Legacy publishing a dead end is the fact worth keeping, and it stops being visible the moment a working link replaces it."}},"required":["code","name","type","results","records"]}}},"required":["meta","page","pwsid","reading","analytes"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/coliform-samples":{"get":{"summary":"Coliform and microbial sample results","tags":["water systems"],"description":"Water samples analysed for organisms — Total Coliform, E. coli, Giardia and Cryptosporidium — each with every microbial result on it inline. This is the agency's own Coliform/Microbial Sample Results page.\n\n**A microbial result has no concentration.** The analysis answers presence or absence and nothing else, so these results carry resultKind \"presence-absence\" and have no concentration field. That is a property of the analysis, not a value the agency failed to record. Do not show it as a gap, and do not compare it to a chem/rad result whose concentration is null, which means something else.\n\n`resultType` uses the same three states as /samples, because the help topic defines this column as whether \"results of an analysis were positive (P-Presence) or negative (A-Absence)\". `reported` carries the agency's own code and word beside it.\n\n  detected      — the organism was present. reported.code is P.\n  not-detected  — the organism was absent. reported.code is A.\n  not-reported  — no indicator was recorded.\n\nPresent results are a small minority here, so a run of absences is the ordinary case and says nothing in itself. not-reported may occur zero times in an installation; handle it anyway.\n\nA present result is not a violation. The Total Coliform Rule and the Revised Total Coliform Rule turn positive samples into violations through repeat sampling, assessment triggers and monthly counts. The agency makes and records those determinations separately, at GET /v1/water-systems/{pwsid}/violations. Counting positives here and calling the answer a compliance finding would present your arithmetic as the agency's.\n\nFive analytes, not three. The agency selects every analyte typed as an organism: 3100 COLIFORM (TCR), 3014 E. COLI, 3008 GIARDIA LAMBLIA, 3015 CRYPTOSPORIDIUM and 3013 FECAL COLIFORM. \"The three coliform codes\" is a different list, the one the chem/rad page excludes.\n\nBy default this returns every sample on record. The agency's page defaults to the last two years; `legacyDefault` in the response has the request that reproduces it.\n\nChem/rad results are not here: GET /v1/water-systems/{pwsid}/samples. GET /v1/water-systems/{pwsid}/analytes lists every analyte a system has been sampled for and links each one to the collection that holds its records.","parameters":[{"schema":{"type":"string"},"in":"query","name":"analyte","required":false,"description":"Only samples analysed for this analyte code. Those samples arrive with every result on them, including results for other organisms: the filter selects samples and does not prune results, as on /samples.\n\nThe agency's coliform page has only a date form, so this filter is ours, not the agency's. GET /v1/water-systems/{pwsid}/analytes lists the codes this system has and links each to whichever endpoint holds its records. See parameters in the response."},{"schema":{"type":"string"},"in":"query","name":"collectedFrom","required":false,"description":"Only samples collected on or after this date (yyyy-mm-dd)."},{"schema":{"type":"string"},"in":"query","name":"collectedTo","required":false,"description":"Only samples collected on or before this date (yyyy-mm-dd)."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Samples per page, not results. Each sample carries every microbial result on it, usually one or two."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Samples to skip."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string"},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string","description":"Which set of samples this response counted, in a sentence."},"findings":{"type":"object","description":"**The whole record this request matches, across every page.** Read it before you read a row, and lead with it if you are writing for a person.\n\nA page is not the record. Samples arrive newest first and most are absences, so a system can have no positive result in its newest page while its record holds positives, including recent ones. Summarising only the rows you fetched could report no positives for a year that had some.\n\nIt covers the same query as the rows: the analyte filter and the date window both apply, so filtering to one organism gives that organism's counts. `scope` says which in words.","properties":{"violations":{"type":"object","description":"Present only when this block reports 0 detected over the whole record (no date window, sampling point, lab number or collection date) and the agency holds validated violations recorded against analyte codes this block covers. Absent otherwise. Which block covers a contaminant is the analyte's own type code: MOR for the microbiological block, any other for chem/rad, and RL (a rule, not an analyte a sample is tested for) for neither. With analyte=, the codes narrow to that analyte exactly as this block does. count is reproducible from `all` by analyte.code. It says nothing about what those violations mean.","properties":{"count":{"type":"integer","description":"Validated violations recorded against the analyte codes in analyteCodes, grouped records included."},"analyteCodes":{"type":"array","items":{"type":"string"},"description":"The agency's analyte codes those violations are recorded against. Rule codes (type RL) are never among them."},"all":{"type":"string","description":"The list that reproduces count: every validated violation, grouped included."},"note":{"type":"string"}},"required":["count","analyteCodes","all","note"]},"scope":{"type":"string","description":"Which set these counts are over, in a sentence."},"counts":{"type":"string","description":"What the numbers count: results and samples, not incidents."},"results":{"type":"object","description":"The same four keys a single sample's findings carries, over every result the query matches. `detected` is the count of positive results.","properties":{"detected":{"type":"integer"},"notDetected":{"type":"integer"},"notReported":{"type":"integer"},"total":{"type":"integer"}},"required":["detected","notDetected","notReported","total"]},"samples":{"type":"object","description":"Samples, not results. withADetection counts samples carrying at least one positive result, so it is at most `detected` and usually below it: one sample can carry several positives.","properties":{"withADetection":{"type":"integer"},"total":{"type":"integer"}},"required":["withADetection","total"]},"mostRecentDetection":{"type":["null","string"],"description":"Collection date of the most recent positive result in scope, or null if there has never been one in scope. The absence is stated as a missing date rather than as a zero somewhere else."},"collected":{"type":"object","description":"The span the counts cover — useful for saying \"no positives since 2003\" rather than \"no positives\", which are different claims.","properties":{"earliest":{"type":["null","string"]},"latest":{"type":["null","string"]}},"required":["earliest","latest"]},"inTheDefaultWindow":{"type":"object","description":"The same counts over the last two years — what the agency's own page shows by default. Present only when you supplied no date window, because a two-year count inside another range answers neither question. Use it to explain a disagreement with the agency's page rather than to conclude one of the two is wrong.","properties":{"from":{"type":"string","format":"date","description":"The earliest collection date these counts include -- the date actually applied."},"note":{"type":"string"},"results":{"type":"object","properties":{"detected":{"type":"integer"},"total":{"type":"integer"}},"required":["detected","total"]},"samples":{"type":"object","properties":{"withADetection":{"type":"integer"},"total":{"type":"integer"}},"required":["withADetection","total"]}},"required":["note","results","samples"]}},"required":["scope","counts","results","samples","mostRecentDetection","collected"]},"parameters":{"type":"object","description":"Which of these filters the agency's page offers, and which were added here. A request using only `fromTheAgencyPage` can be reproduced on the agency's page; one using `addedHere` cannot, so \"as the agency shows it\" is true only of the first kind. Every `addedHere` filter narrows records the agency already publishes; none reaches a fact it does not.","properties":{"fromTheAgencyPage":{"type":"array","items":{"type":"string"}},"addedHere":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}},"required":["fromTheAgencyPage","addedHere","note"]},"legacyDefault":{"type":"object","description":"How this endpoint differs from the agency's page, and the request that makes them agree. A default, not a boundary — no fact is added or withheld.","properties":{"note":{"type":"string"},"reproduceIt":{"type":"string"}},"required":["note","reproduceIt"]},"window":{"type":"object","description":"The date bounds applied to this listing, on the collection date. Present only when bounds are in force. `source` is present only when this API chose them; its absence means they are the ones you passed. See `notShown` for how many records they excluded.","properties":{"collectedFrom":{"type":"string","format":"date"},"collectedTo":{"type":"string","format":"date"},"source":{"type":"string"}}},"notShown":{"type":"object","description":"Present only when a date window hides samples that exist.","properties":{"count":{"type":"integer"},"why":{"type":"string"},"everything":{"type":"string"}},"required":["count","why","everything"]},"samples":{"type":"array","items":{"type":"object","description":"One sample, with every microbial result recorded against it.","properties":{"labSampleNumber":{"type":["null","string"],"description":"The number the laboratory assigned, as the agency prints it. **It is not an identifier:** it repeats within a single water system, so there is no per-sample URL and every result travels inline."},"sampleType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Why the sample was taken. TG has a null label because the help topic does not define it; its entry in /v1/code-tables/sampleType carries a note. Repeat samples are the follow-up a system collects after a positive routine sample, so a run of RP rows around one RT is the rule working, not a second incident. Null when the underlying record has no value for this field."},"collected":{"type":"object","properties":{"date":{"type":["null","string"],"description":"Collection end date (ISO 8601)."},"time":{"type":["null","string"],"description":"Collection end time as HH:MM:SS, when recorded. Seconds are included because the agency prints them and some samples carry a non-zero one."}},"required":["date","time"]},"samplingPoint":{"type":"object","description":"Where the sample was taken. For coliform this is usually a point in the distribution system rather than a source or an entry point.","properties":{"id":{"type":["null","string"]},"location":{"type":["null","string"],"description":"**The location may be withheld.** Where a point identifies a private dwelling this API publishes null and says so in dataQuality on the same object -- our decision, not an absence in the agency's records. The id, the type and every result taken there are unaffected."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["id","location"]},"laboratory":{"type":["null","string"],"description":"The accredited laboratory that ran the analysis. Rarely null: the agency's own query requires an active accreditation."},"findings":{"type":"object","description":"Counts of the results in `results`, by the same three states. `detected` is the count of positive results on this sample. Deliberately not a boolean: false would merge \"nothing was found\" into \"nothing was reported\". total is the length of the results array, so the count and the rows cannot disagree.","properties":{"detected":{"type":"integer"},"notDetected":{"type":"integer"},"notReported":{"type":"integer"},"total":{"type":"integer"}},"required":["detected","notDetected","notReported","total"]},"results":{"type":"array","description":"Every microbial result on this sample. Complete and never paged — a coliform sample carries only a handful of results, and most carry one.","items":{"type":"object","properties":{"analyte":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"],"description":"The agency's own name for the organism."},"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The analyte type. Always MOR, organism, on this endpoint. Null when the underlying record has no value for this field."}},"required":["code","name","type"]},"resultKind":{"type":"string","enum":["concentration","presence-absence"],"description":"Always \"presence-absence\" here, and published rather than implied so a consumer branches on it instead of on a missing key. There is NO concentration field on this kind of result: the analysis produces no quantity. The full vocabulary is at /coverage."},"resultType":{"type":"string","enum":["detected","not-detected","not-reported"],"description":"detected: the organism was PRESENT. not-detected: it was ABSENT. not-reported: no indicator was recorded, which may not occur at all in a given installation. Same enum as /samples on purpose — one vocabulary for one idea."},"reported":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The agency's own indicator and its own word for it, verbatim: P Presence, A Absence. Null only when nothing was recorded. Show this to a person and use resultType to decide how. Null when the underlying record has no value for this field."},"monitoringPeriod":{"type":"object","description":"The compliance period this result counts toward, where the agency linked one. The agency's page says in its own words that results appear \"regardless if the sample result is or is not associated to a monitoring period\", and a small number have none.","properties":{"begins":{"type":["null","string"]},"ends":{"type":["null","string"]}},"required":["begins","ends"]},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["analyte","resultKind","resultType","reported","monitoringPeriod","dataQuality"],"description":"**The agency's data-quality code here is not a neutral provenance field.** Validation is what happens to a positive: the help topic says an Accepted result \"must be validated by state personnel before the result is used in automated noncompliance determinations\". So \"Validated\" here goes with \"Presence\" almost every time, and it does not mean the result is more reliable than an Accepted one. Do not present it as a confidence rating, or let a reader infer the finding from it."}}},"required":["labSampleNumber","sampleType","collected","samplingPoint","laboratory","findings","results"]}}},"required":["meta","page","pwsid","reading","findings","parameters","legacyDefault","samples"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/coliform-summaries":{"get":{"summary":"Coliform sample summaries — served, and may be empty for every system","tags":["water systems"],"description":"The number of routine coliform-NEGATIVE samples recorded for each monitoring period. This is the agency's own Coliform Sample Summary Results page.\n\n**This may return nothing for every water system, and that can be the correct answer.** Where an agency records its coliform monitoring as individual samples rather than as summaries, every sample summary it holds is a lead or copper 90th-percentile summary and there is not one coliform summary for any system, any period or any data quality. The agency's own page then prints \"Total Number of Records Displayed = 0\" on every system it is opened on. Check /coverage and this endpoint's own reading before concluding anything from an empty result.\n\nSo an empty result here says nothing about the system you asked about. It is a fact about what the agency loads, not about that town's water or its records. Reporting \"no coliform summaries on file\" for one system as though it distinguished that one from any other would be false. The endpoint exists precisely so you are told this rather than left to infer it from a 404 or a bare empty array.\n\nThe record you actually want is GET /v1/water-systems/{pwsid}/coliform-samples, which serves the individual coliform results for every system that has them, each with a presence or absence verdict. Count the absences there if you need a negative count; say that you counted them.\n\nThe query is the agency's, reproduced exactly, so this endpoint starts publishing on its own the day a summary is loaded — here or at another installation.","parameters":[{"schema":{"type":"string"},"in":"query","name":"periodFrom","required":false,"description":"Only monitoring periods beginning on or after this date (yyyy-mm-dd)."},{"schema":{"type":"string"},"in":"query","name":"periodTo","required":false,"description":"Only monitoring periods beginning on or before this date (yyyy-mm-dd)."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Summaries per page."},{"schema":{"type":"integer","minimum":0,"maximum":1000000,"default":0},"in":"query","name":"offset","required":false,"description":"Summaries to skip."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string"},"window":{"type":"object","description":"The date bounds applied to this listing, on the monitoring-period start date. Present only when bounds are in force. `source` is present only when this API chose them; its absence means they are the ones you passed.","properties":{"periodFrom":{"type":"string","format":"date"},"periodTo":{"type":"string","format":"date"},"source":{"type":"string"}}},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string","description":"Read this before reporting the empty array. It states the population the emptiness was measured over — every summary this installation holds, a figure the reading itself carries — so the absence cannot be mistaken for a gap in this system's records."},"summariesLoaded":{"type":["null","object"],"description":"Present only when this collection is empty. What sample summaries the installation actually holds, by analyte, counted at request time — so an empty page can be read as \"none loaded\" or \"loaded, but of another kind\" rather than as a fact about this system.","properties":{"note":{"type":"string"},"analytes":{"type":"array","items":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"summaries":{"type":"integer"}},"required":["code","name","summaries"]}}},"required":["note","analytes"]},"coliformSamples":{"type":"string","description":"The endpoint that does hold this system's coliform record."},"legacyDefault":{"type":"object","properties":{"note":{"type":"string"},"reproduceIt":{"type":"string"}},"required":["note","reproduceIt"]},"summaries":{"type":"array","description":"Populated only where the agency loads coliform summaries; `summariesLoaded` reports what this deployment actually holds, measured at request time. The shape is declared in full because the query is the agency's and would populate it the day a summary is loaded.","items":{"type":"object","properties":{"analyte":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}},"required":["code","name"]},"monitoringPeriod":{"type":"object","properties":{"begins":{"type":["null","string"]},"ends":{"type":["null","string"]}},"required":["begins","ends"]},"negativeCount":{"type":["null","integer"],"description":"The agency's \"Number of Routine Negatives\": how many routine samples in this period came back absent for coliform. Zero is a recorded count (no routine sample in the period came back negative), not a missing value."},"resultQuality":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The quality status of the summary. Only Accepted and Validated would appear, because the agency's own page lists only those two. Null when the underlying record has no value for this field."},"facilityId":{"type":["null","string"],"description":"The facility the summary is attributed to. Resolve it with GET /v1/water-systems/{pwsid}/facilities/{facilityId}."},"summaryReceivedDate":{"type":["null","string"],"description":"When the agency received the summary (ISO 8601)."}},"required":["analyte","monitoringPeriod","negativeCount","resultQuality","facilityId","summaryReceivedDate"]}}},"required":["meta","page","pwsid","reading","coliformSamples","legacyDefault","summaries"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/facilities":{"get":{"summary":"Facilities","tags":["water systems"],"description":"Every facility of this water system — wells, intakes, treatment plants, storage, distribution zones — which is what the agency's own Water System Facilities page lists.\n\nIncludes inactive facilities, and no parameter removes them, because the agency's page applies no such filter. Read facilityStatus on every row. The order is the agency's: status, then type, then identifier — which puts active facilities first as a side effect of the codes sorting that way, not as a rule you should rely on.\n\nGET /v1/water-systems/{pwsid}/sources is a SUBSET of this list, filtered to the eight facility types that are water sources. If you want to know where the water comes from, use that; if you want everything the system is made of, use this.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these facilities belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"scope":{"type":"string","description":"What this list does and does not contain, stated rather than left to be inferred from the rows."},"facilities":{"type":"array","items":{"type":"object","description":"One facility of the water system.","properties":{"facilityId":{"type":["null","string"],"description":"The identifier the agency assigns a facility, unique within a public water system. Published TRIMMED, because some are stored with a leading space that is invisible wherever it is rendered, and an identifier a reader cannot retype is not an identifier. Use it as {facilityId} in /v1/water-systems/{pwsid}/facilities/{facilityId}; matching is case-insensitive and ignores surrounding space."},"name":{"type":["null","string"],"description":"Facility name, as the agency records it."},"facilityType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Kind of facility — well, treatment plant, storage, distribution zone and so on. Note that an ENTRY POINT is not a facility type: it is a sampling-point designation, and entry points appear under samplingPoints on the facility detail. Null when the underlying record has no value for this field."},"facilityStatus":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the facility is in service. This list includes inactive facilities, so read this on every row rather than assuming a listed facility is operating. /v1/code-tables/facilityStatus decodes it. Null when the underlying record has no value for this field."},"detail":{"type":["null","string"],"description":"The request that returns this facility with its sampling points and operating periods."}},"required":["facilityId","name","facilityType","facilityStatus","detail"]}}},"required":["meta","page","pwsid","scope","facilities"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/facilities/{facilityId}":{"get":{"summary":"One facility","tags":["water systems"],"description":"One facility, with its active sampling points and its annual operating periods: the three tables the agency renders on one page.\n\nSampling points are where samples are taken, and an entry point is one of them. EP is a sampling-point type, not a facility type. The agency places entry points on the facility the water flows from, not on a distribution system.\n\nRead samplingPointsScope before concluding anything from an empty list: only active sampling points are listed, which is the agency's own filter, so an inactive facility showing none may well have had them.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true},{"schema":{"type":"string"},"in":"path","name":"facilityId","required":true,"description":"The facility's state-assigned identifier, as published by GET /water-systems/{pwsid}/facilities. Matched case-insensitively and ignoring surrounding whitespace. PERCENT-ENCODE IT: many of these identifiers contain a space. A slash would need encoding too and none currently has one, so do not rely on that."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"pwsid":{"type":"string","description":"The system this facility belongs to."},"facility":{"type":"object","properties":{"facilityId":{"type":["null","string"],"description":"The identifier the agency assigns a facility, unique within a public water system. Published TRIMMED, because some are stored with a leading space that is invisible wherever it is rendered, and an identifier a reader cannot retype is not an identifier. Use it as {facilityId} in /v1/water-systems/{pwsid}/facilities/{facilityId}; matching is case-insensitive and ignores surrounding space."},"name":{"type":["null","string"],"description":"Facility name, as the agency records it."},"facilityType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Kind of facility — well, treatment plant, storage, distribution zone and so on. Note that an ENTRY POINT is not a facility type: it is a sampling-point designation, and entry points appear under samplingPoints on the facility detail. Null when the underlying record has no value for this field."},"facilityStatus":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the facility is in service. This list includes inactive facilities, so read this on every row rather than assuming a listed facility is operating. /v1/code-tables/facilityStatus decodes it. Null when the underlying record has no value for this field."},"detail":{"type":["null","string"],"description":"The request that returns this facility with its sampling points and operating periods."},"activityReason":{"type":["null","string"],"description":"Why the facility is in that status, as the agency recorded it — the difference between a well that was abandoned and one held on standby. Operator FREE TEXT, not a code: wording and case are inconsistent. The agency's own page prints an empty cell when there is none, so a null here means the agency has no text on record, not that this API dropped it. Quote it, do not parse it."},"activityDate":{"type":["null","string"],"description":"When the facility entered its current activity status (ISO 8601). Null when the agency has no date on record, which for a substantial minority of facilities means the SDWIS placeholder 1901-01-01, reported as null with a dataQuality flag saying so. The records page prints the placeholder as a date."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["facilityId","name","facilityType","facilityStatus","detail","activityReason","activityDate","dataQuality"]},"samplingPointsScope":{"type":"string","enum":["active"],"description":"Which points `samplingPoints` contains — a statement about rows, and only about rows. \"active\" means the agency's page omits inactive points, and so does this response. Machine-readable so a consumer can say so on a page rather than presenting an empty list as a fact about the agency's records. No point is omitted for any reason of ours. Where this API withholds a location that identifies a private dwelling, it keeps the row, nulls that one field, and says so in dataQuality on the row. That cannot shorten this list."},"samplingPointsNote":{"type":"string"},"samplingPoints":{"type":"array","items":{"type":"object","description":"A point on this facility where samples of the water are taken.","properties":{"samplingPointId":{"type":["null","string"],"description":"The identifier the state assigns to this sampling point. Sample results cite it."},"description":{"type":["null","string"],"description":"Where the point is, in the agency's own words. Free text. **The location may be withheld.** Where a point identifies a private dwelling this API publishes null and says so in dataQuality on the same object -- our decision, not an absence in the agency's records. The id, the type and every result taken there are unaffected."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"},"samplingPointType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Where the point is or what it is for. EP is the entry point to the distribution system. **Expect this field to be null, and often.** The whole object is null, not an object with a null label. Untyped points are commonly the largest group; on some systems most distribution-system points carry no type. Check for null before reading .code or .label. This API reports the absence rather than guessing a type. Null when the underlying record has no value for this field."}},"required":["samplingPointId","description","samplingPointType"]}},"annualOperatingPeriodsNote":{"type":"string"},"annualOperatingPeriods":{"type":"array","description":"The months of the year this FACILITY operates. NOT the system operating period from GET /v1/water-systems/{pwsid}/operating-periods, which is a different table and is where population served comes from. Sparse — 26 rows exist in the whole state, so empty is the normal case.","items":{"type":"object","properties":{"startMonth":{"type":["null","integer"],"description":"Month the operating window opens, 1-12."},"startDay":{"type":["null","integer"],"description":"Day of that month."},"endMonth":{"type":["null","integer"],"description":"Month the window closes, 1-12."},"endDay":{"type":["null","integer"],"description":"Day of that month."},"effectiveBeginDate":{"type":["null","string"],"description":"First date this window applied (ISO 8601). Null means the agency recorded no date — check dataQuality, which distinguishes the SDWIS 1901-01-01 placeholder from a genuinely empty column. Do not read a null here as \"open-ended\"; that is what a null END date means."},"effectiveEndDate":{"type":["null","string"],"description":"Last date it applied, or null if it still does."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["startMonth","startDay","endMonth","endDay","effectiveBeginDate","effectiveEndDate","dataQuality"]}},"analyteLevels":{"type":"object","description":"The agency's FANL List page for this facility — the analyte levels it has designated here. EVERY level on record, including superseded ones: the agency applies no date filter and neither does this, so read `effective` rather than assuming a row is in force. Most facilities that have any designated level have several, so an empty list is worth reading as \"none designated here\" rather than as an absence of regulation.","properties":{"count":{"type":"integer"},"scope":{"type":"string","description":"The per-request statement of what this listing is of. **Read the value before concluding anything from count: 0.** It is the only place the narrowing applied to this request is stated. This object's own description says what the narrowing normally is."},"levels":{"type":"array","items":{"type":"object","properties":{"analyte":{"type":["null","object"],"description":"The analyte the level applies to.","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}}},"controlLevelType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the level is a maximum, a minimum, an average or a 95th percentile. The application defines all four; where a deployment publishes no code table for it the labels are the built-in ones, and labelSource says which you have. Null when the underlying record has no value for this field."},"measure":{"type":["null","string"],"description":"The level as the agency records it — CHARACTER data, published verbatim and NOT converted to a number. The same analyte can carry the same level written two ways — \"0.080\" and \".080\" — so do not assume it parses, or that two rows with equal values are equal strings."},"unit":{"type":["null","string"],"description":"Unit of measure, as recorded."},"effective":{"type":"object","description":"When this level applied. A set `end` means the row has been superseded; a null `end` means the agency recorded none.","properties":{"begins":{"type":["null","string"],"format":"date"},"ends":{"type":["null","string"],"format":"date"}},"required":["begins","ends"]},"summaryType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Which kind of MDBP summary the level belongs to. The agency publishes this domain in its own code table, so labelSource is normally sdwis-code-table. Null where the agency recorded none, which is most rows. Null when the underlying record has no value for this field.","type":["object","null"]}},"required":["analyte","controlLevelType","measure","unit","effective","summaryType"]}},"summaries":{"type":"string","description":"The request that returns the MDBP summaries assessed against these levels — the agency's Summarized Field Sample Results for this facility. Linked from here because this is the payload that owns them; without it the resource was reachable only from the OpenAPI document."}},"required":["count","scope","levels","summaries"]}},"required":["meta","pwsid","facility","samplingPointsScope","samplingPointsNote","samplingPoints","annualOperatingPeriodsNote","annualOperatingPeriods"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/sources":{"get":{"summary":"Water sources","tags":["water systems"],"description":"The facilities this system draws water from.\n\nIncludes INACTIVE sources, and for many systems most of them are inactive — a retired well stays on the record permanently. Check facilityStatus on every row before describing where a system gets its water, and read activityReason before calling an inactive source retired: standby and abandoned are both inactive here. A system whose only active source is a Consecutive Connection is buying its water; GET /v1/water-systems/{pwsid}/purchases names the seller.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"sources":{"type":"array","items":{"type":"object","description":"A source of water — a well, intake, spring, reservoir or purchased connection.","properties":{"facilityId":{"type":["null","string"],"description":"Agency-assigned facility identifier, unique within the system."},"name":{"type":["null","string"],"description":"Facility name."},"facilityType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Kind of source. Null when the underlying record has no value for this field."},"facilityStatus":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether this source is currently in service. /v1/code-tables/facilityStatus decodes it. Null when the underlying record has no value for this field."},"activityReason":{"type":["null","string"],"description":"Why the facility is in that status, as the agency recorded it. This is the difference between a well that was abandoned and a well held on standby, which the status code alone cannot tell you. Operator FREE TEXT, not a code: wording and case are inconsistent, most active sources have none, and some entries reference internal record numbers. Quote it, do not parse it, and do not read a null as \"no reason\"."}}}}},"required":["meta","page","pwsid","sources"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/milestones":{"get":{"summary":"Enforcement milestones","tags":["water systems"],"description":"Lead, copper and corrosion-control enforcement events for one system. The help topic describes milestones as events \"related to the enforcement of various rules\".\n\nThe type is the finding, not the value. PB90 and CU90 are the agency's own codes for a lead or copper 90th-percentile action level exceedence, so a system with a PB90 row passed the lead action level on that date. The accompanying figure carries no unit on this table and none is invented here — the agency publishes the same quantity stamped MG/L on /lead-and-copper, so go there for a unit rather than assuming one. Read type, and read value.means before using value.number for anything.\n\nthe agency's own page serves VALIDATED milestones only and so does this endpoint. It orders newest first; the agency's page publishes no order at all.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"milestones":{"type":"array","items":{"type":"object","description":"One enforcement milestone. The help topic describes these as events \"related to the enforcement of various rules (e.g., lead and copper)\". Which rules a deployment actually records milestones for is a question about its data: read `type`, whose code table covers filtration and source-water treatment as well as lead and copper.","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"What kind of event this is, and on this record type the TYPE carries the finding. PB90 and CU90 are the agency's codes for a lead or copper 90th-percentile action level EXCEEDENCE — the row exists because a limit was passed, not because a measurement was filed. Null when the underlying record has no value for this field."},"reason":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Why it was recorded. B1 and B3 are about POPULATION SERVED (under or over 50,000), not about the contaminant. Null when the underlying record has no value for this field."},"statusSetOn":{"type":["null","string"],"description":"The date the STATUS was set (ISO 8601) — the definition the help topic published with these pages gives (\"the date that the milestone event status was set\"), and not necessarily the date the event happened. No date for the event itself is published anywhere."},"value":{"type":["null","object"],"description":"The recorded figure, WITHOUT a unit, because THIS TABLE carries no unit column. That is not the same as no unit existing: the agency publishes the same quantity stamped MG/L on the lead-and-copper page, which is the place to go if you need one. Read value.means before using it — what the number represents depends on the milestone type. Do not compare it to an action level; the type already tells you whether one was exceeded.","properties":{"number":{"type":"number","description":"The figure as a number."},"unit":{"type":"null","description":"Always null. There is no unit column in the source, no unit in the help topic, and none on the page. Published as an explicit null rather than omitted so that its absence is a stated fact rather than a gap you might assume we forgot to fill."},"means":{"type":"string","description":"What the figure represents, and its limits."}},"required":["number","unit","means"]},"status":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the record is validated. Always V here: the agency's own page serves validated milestones only, so this field is published because the agency publishes it, not because it varies. Null when the underlying record has no value for this field."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}}}}},"required":["meta","page","pwsid","milestones"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/sample-schedules":{"get":{"summary":"Sampling schedules","tags":["water systems"],"description":"What this system has to sample, how often, and over what period — the four schedule tables the agency's own page prints on its Sample Schedules page, in the order it prints them.\n\n**Read `kind` before comparing any two rows.** The agency stores a hidden per-analyte schedule for every member of a group and displays only the group, and this list follows the page. Counting rows counts what the agency shows, not how many schedules exist.\n\nA schedule that ended up to six months ago is still listed. That is the agency's own window on all four of these queries, not a choice made here.\n\nThe agency's page has two more columns on the non-TCR tables, \"Current Monitoring Period Date Range\" and \"Next Monitoring Period Date Range\". The current one is served, as currentMonitoringPeriod: the agency's page looks up the stored monitoring period that contains today and prints its dates. Read `reading` for what those dates mean. They are the window this schedule applies to inside that period, so on a seasonal or multi-year schedule they can have ended already or not yet begun. The next period is not served; the agency's page builds it from two more queries per row.\n\nEither cell can show a phrase instead of dates on the agency's page: \"No Monitoring in Current Period\", \"No Monitoring Required\", \"One Time Monitoring\", \"Monitoring Completed\" or \"Monitoring Partially Completed\". The last two count results against a required sample count. This API reproduces none of these phrases. So do not infer from a null currentMonitoringPeriod that a schedule has none: on a tcr row SDWIS holds no monitoring period at all.\n\nThe agency's own disclaimer, quoted: \"A monitoring violation may occur if samples were not collected from the appropriate sampling point, were not analyzed by a certified laboratory, or were not reported on time.\" **A schedule on file is a requirement, not evidence that it is being met.** Do not read this endpoint as a compliance record. The agency adds that its monitoring-period message never applied to \"Total Coliform Rule (TCR), triggered, confirmation or repeat schedules or schedules with a daily or weekly frequency\". Here that means kind \"tcr\" and any schedule whose sampleType is repeat or confirmation.\n\nThe agency shows enforcement schedule activities on this same page; they are served separately at /compliance-schedules because they share no field with these.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"notShown":{"type":"object","description":"Present when the default window excluded schedules. Without it, an empty list looks the same whether this system has no schedules or all of them ended before the window. The second is common, so an empty list must never be read as the first. `count` says how many the window excluded.","properties":{"count":{"type":"integer","description":"How many schedules the window excluded."},"why":{"type":"string"}},"required":["count","why"]},"sampleSchedules":{"type":"array","items":{"type":"object","description":"One sampling schedule. **Four kinds share this shape, and `kind` says which.** The agency prints them as four tables under one heading, and they are not interchangeable.","properties":{"kind":{"type":"string","enum":["tcr","fanl","non-tcr-group","non-tcr"],"description":"\"tcr\" is the coliform schedule and is held at water-system level, so it has no facility. \"fanl\" is a facility analyte level and is the only kind carrying daysToMonitorPerMonth and samplesRequiredPerDay. \"non-tcr-group\" is a schedule written against an analyte group; \"non-tcr\" is one written against a single analyte. The agency stores a hidden per-analyte schedule for every member of a group and does not display them. So a group row is not one schedule, and counting rows does not count schedules."},"facility":{"type":["null","object"],"description":"The water system facility the schedule is written against, or null for a TCR schedule, which the agency holds at system level.","properties":{"facilityId":{"type":["null","string"],"description":"The state-assigned identifier."},"name":{"type":["null","string"],"description":"The facility name."}}},"analyte":{"type":["null","object"],"description":"The analyte scheduled, for tcr, fanl and non-tcr kinds. Null on a group.","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}}},"analyteGroup":{"type":["null","object"],"description":"The analyte GROUP scheduled, on non-tcr-group rows only. Null everywhere else. Filtering on analyte.code will not match these, which is deliberate. `members` links to the analytes the group stands for.","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"members":{"type":"string","description":"The analytes this group stands for. Nothing else on this payload expands it, and no analyteType filter reproduces it: a type is a property of an analyte and membership is a record the agency keeps."}}},"sampleCount":{"type":["null","integer"],"description":"How many samples are required per unit of the count unit. Null on fanl."},"sampleType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Routine, repeat or confirmation. The agency: \"Sample schedules may only be for routine, repeat or confirmation samples.\" Null when the underlying record has no value for this field."},"countUnit":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The period the sample count applies over — QT quarterly, 3Y once every three years. The agency's own page prints this code bare in its Frequency column; the label is the help topic's wording (Sample Frequency), which names nineteen codes. Null when the underlying record has no value for this field.","type":["object","null"]},"effective":{"type":"object","description":"When the schedule itself is in force. Distinct from `season`, which repeats every year within it.","properties":{"begins":{"type":["null","string"],"description":"ISO 8601 date."},"ends":{"type":["null","string"],"description":"ISO 8601 date, or null for open-ended. A schedule that ended within the last six months is still listed — that is the agency's window, not ours."}}},"samplingPoints":{"type":["null","array"],"description":"Where this schedule's samples must be collected, and how many at each — the agency's own popup, inlined. NULL means the agency records no sampling-point list for this KIND of schedule: its page offers the link on the non-TCR schedule and group tables only. That is different from an EMPTY array, which means the schedule can have them and none is recorded — legacy prints that case as \"No Specified Sampling Point\".","items":{"type":"object","properties":{"id":{"type":["null","string"]},"description":{"type":["null","string"],"description":"Where the point is, in the agency's own words. **It may be withheld:** where a point identifies a private dwelling this API publishes null and says so in dataQuality beside it. That is our decision, not an absence in the record."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"},"sampleCount":{"type":["null","number"],"description":"How many of the schedule's samples are to be taken here. A requirement, not a count of samples collected."}},"required":["id","description","sampleCount"]}},"samplingPointsNote":{"type":"string","description":"The qualifier the agency prints above this list, verbatim. A description here can be a street address, which beside a plant name looks more precise than it is; this sentence says not to rely on it as an address. Present only where samplingPoints is."},"season":{"type":["null","object"],"description":"The months and days within each year that the schedule applies to, for a seasonal schedule. Null when the schedule runs year-round — the agency stores four zeros and this API publishes null rather than a date of 0-0.","properties":{"beginMonth":{"type":["null","integer"]},"beginDay":{"type":["null","integer"]},"endMonth":{"type":["null","integer"]},"endDay":{"type":["null","integer"]}}},"stateYear":{"type":["null","integer"],"description":"The agency's state year for the schedule, where it records one — a sequence year within a multi-year cycle, not a calendar year. It is what shifts currentMonitoringPeriod off the stored dates: 0 means the applicable dates are as stored, and any other value moves the period by (stateYear - 1) years."},"monitoringAssessmentFlag":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The monitoring assessment flag, on non-tcr rows: a flag compliance-determination processing sets on the schedule, not a finding about the water. Nothing decodes its values, so each arrives with a null label; /v1/code-tables/monitoringAssessmentFlag says what is known. The agency's own schedules page does not display it. Null when the underlying record has no value for this field."},"violationType":{"type":["null","object"],"description":"The violation type the agency associates with a failure of this schedule, where one is set: its code and the agency's name for it, from the same table /violations reads. Some codes have more than one name in that table (a MAJOR and a MINOR under one code), so read the name with the code. The agency's own schedules page does not display this column at all.","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}}},"daysToMonitorPerMonth":{"type":["null","integer"],"description":"fanl only: days per month on which monitoring is required."},"samplesRequiredPerDay":{"type":["null","integer"],"description":"fanl only: samples required on each of those days."},"summaryType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"fanl only: which summarized field result this schedule is for. The same code set the MDBP summaries use, from the help topic (Summary Type). Null when the underlying record has no value for this field.","type":["object","null"]},"currentMonitoringPeriod":{"type":["null","object"],"description":"The monitoring period this schedule falls in today: the period the agency's own page prints in its \"Current Monitoring Period Date Range\" column, assembled the same way from the same stored values.\n\n**Null here never means \"monitoring is not required\".** On a tcr or fanl row the agency publishes no such column at all. On a group or individual schedule it means no monitoring period on record contains today, which is what the agency's page shows as \"No Monitoring in Current Period\". Read `kind` to tell the first from the other two.\n\nThe agency's page sometimes replaces this cell with \"Monitoring Completed\" or \"Monitoring Partially Completed\", computed from two further queries per row that this API does not run. The period published here is still the period; the completion state is never published.","properties":{"begins":{"type":"string","description":"First day of the period, yyyy-mm-dd."},"ends":{"type":"string","description":"Last day of the period, yyyy-mm-dd."}},"required":["begins","ends"]}}}}},"required":["meta","page","pwsid","sampleSchedules"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/compliance-schedules":{"get":{"summary":"Enforcement schedule activities","tags":["water systems"],"description":"Enforcement schedule activities for one system — what it was required to do and when, with the agency's due, projected, achieved and reported dates.\n\nThis is not the whole schedule history, and the filter is the agency's. The records page shows an activity if it has no due date, or is due within the last 60 days or in future, or is overdue and not yet achieved. What that leaves out is exactly one class: activities due more than 60 days ago that WERE achieved. The page is a list of what is outstanding, so do not read it as an audit trail.\n\nOnly schedules the agency marks final are included, which is also its filter.\n\nThe agency prints these on the same page as the sampling schedules, served here at /sample-schedules. Fetch both to reproduce that page.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"complianceSchedules":{"type":"array","items":{"type":"object","description":"One enforcement schedule activity: what the system was required to do, and the four dates the agency tracks against it.","properties":{"activity":{"type":["null","string"],"description":"The agency's name for the required activity."},"dueOn":{"type":["null","string"],"description":"When it was required (ISO 8601)."},"projectedFor":{"type":["null","string"],"description":"When the agency expected it to happen (ISO 8601)."},"achievedOn":{"type":["null","string"],"description":"When it was done (ISO 8601), or null if it has not been. Null with a past dueOn is an outstanding item."},"reportedOn":{"type":["null","string"],"description":"When the achievement was reported (ISO 8601)."}}}}},"required":["meta","page","pwsid","complianceSchedules"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/site-visits":{"get":{"summary":"Site visits and sanitary surveys","tags":["water systems"],"description":"Inspections and sanitary surveys the agency has carried out at this system, newest first, with what was found.\n\nWhat the two deficiency fields mean. deficienciesFound is the agency's count of MINOR and SIGNIFICANT deficiencies, which is what its own help text defines the field as: \"the number of significant and minor deficiencies found during a sanitary survey/site visit\". highestDeficiency reports the highest finding of any kind, including a recommendation — and of REC that same help text notes that \"a recommendation is not considered a deficiency\". So a visit can carry highestDeficiency = REC with deficienciesFound = 0: a recommendation was made and no deficiency was found. Both numbers are complete as they stand.\n\nThe individual findings — what each deficiency actually was — are NOT published here or by the agency. Counts and severities are all there is, so \"3 significant deficiencies, all resolved\" is the most specific true statement available.\n\nCOMPLETED visits only, matching the public application. A system with an empty list has never been visited, not \"has no deficiencies\".\n\nNo date window is applied unless you ask for one, and the agency's own page applies a two-year default — see legacyDefault in the response for the request that reproduces it.","parameters":[{"schema":{"type":"string","format":"date"},"in":"query","name":"visitedFrom","required":false,"description":"Only visits made on or after this date (ISO 8601). Named for the column it bounds: the visit date, which is neither a collection date nor a monitoring period, so it is spelled apart from the collectedFrom/periodFrom its sibling collections take."},{"schema":{"type":"string","format":"date"},"in":"query","name":"visitedTo","required":false,"description":"Only visits made on or before this date (ISO 8601)."},{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"The date bounds applied to this listing, on the visit date. Present only when bounds are in force. `source` is present only when this API chose them; its absence means they are the ones you passed. See `notShown` for how many records they excluded.","properties":{"visitedFrom":{"type":"string","format":"date"},"visitedTo":{"type":"string","format":"date"},"source":{"type":"string"}}},"legacyDefault":{"type":"object","description":"The date default the agency's own page applies and this endpoint does not, with the request that reproduces it.","properties":{"note":{"type":"string"},"reproduceIt":{"type":"string"}},"required":["note","reproduceIt"]},"notShown":{"type":"object","description":"Present only when the visit-date window hides completed visits that exist.","properties":{"count":{"type":"integer"},"why":{"type":"string"},"everything":{"type":"string","description":"The request that returns every completed visit on record."}},"required":["count","why","everything"]},"siteVisits":{"type":"array","items":{"type":"object","description":"One completed visit by the agency to the system — a scheduled sanitary survey, an inspection, or an assessment triggered by a sample result.","properties":{"visitDate":{"type":["null","string"],"description":"Date of the visit (ISO 8601)."},"reason":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Why the agency visited. Null when the underlying record has no value for this field."},"highestDeficiency":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Severity of the highest finding identified at this visit, including any associated from earlier visits: SIG significant, MIN minor, REC recommendation made, NON none. The application's help text defines the domain and notes, of REC, that \"a recommendation is not considered a deficiency\". Null when the underlying record has no value for this field."},"deficienciesFound":{"type":"integer","description":"Number of MINOR and SIGNIFICANT deficiencies recorded at this visit. This is the agency's own count, and it EXCLUDES recommendations, so a visit can have highestDeficiency = REC and deficienciesFound = 0 at the same time."},"deficienciesResolved":{"type":"integer","description":"How many of those the agency has since recorded as resolved."},"deficienciesOutstanding":{"type":"integer","description":"deficienciesFound minus deficienciesResolved — how many are still open. Derived here for convenience; the agency publishes the two counts, not this difference."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}}}}},"required":["meta","page","pwsid","siteVisits"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/contacts":{"get":{"summary":"Points of contact","tags":["water systems"],"description":"People and addresses on record for the system. Not every row is a person: sampling and delivery addresses are filed under contact roles, and the public app renders both in one table under a job-title column, which makes addresses look like staff. Check dataQuality before presenting any row as a person. Contact details are published only for administrative contacts and owners — that restriction is the agency's, made in the public application, and preserved here.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"contacts":{"type":"array","items":{"type":"object","description":"A point of contact for the system. Not necessarily a person — see isIndividual.","properties":{"name":{"type":["null","string"],"description":"Name as recorded. May be an organisation or an address label rather than a person."},"entityId":{"type":["null","string"],"description":"Identifies the recorded entity (person, organisation or address label) behind this row. One entity holding several roles appears once per role, as in the agency's own application; rows with the same entityId are the same entity, so group on it rather than on name — two different people can share a name. Opaque: it is stable, but it is not a register key."},"contactType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"The contact's RELATION to the water system, and the field to filter on if you want only people: AC administrative contact, OW owner, OP operator, DO designated operator, SA sampler, EC emergency, OT other. The agency treats AC and OW as the contactable roles — its own application publishes phone, address and email for those two and blanks them for every other type, and this API reproduces that exactly.\n\nWhich of these are people, because \"filter on contactType\" is useless advice without it. AC, OW, DO, OP and EC are people, and each such row has an individual record behind it. SA and OT are overwhelmingly ROUTING ENTRIES: their names are built from the system they route for, so a name like \"BACTI\" followed by the system's own identifier is a mailbox, not a person. Filter to AC, OW, DO, OP and EC if you want people; take a name from SA or OT as an address label.\n\nAnd note where the two signals part company: many OT rows have an individual record anyway. The register holds individual records for entities whose own names are plainly routing identifiers, which is why individualRecord.present is evidence and not proof. Null when the underlying record has no value for this field."},"individualRecord":{"type":"object","description":"Whether SDWIS holds an individual (person) record for this entity. A contact is a legal entity, which may be a person, a company, or a mailing destination someone filed as a contact.\n\nThis reports what the register CONTAINS, not what is true, and the difference is real: a routing or address entry filed as a contact can carry one, which is a data-entry error, and a person can lack one. So present=true is EVIDENCE that a contact is a person and is not proof of it, and present=false is the stronger of the two signals. It is not called isPerson for that reason, and it is deliberately not merged with the name-looks-like-identifier data-quality flag — the two disagree on that row, and combining them would manufacture a confidence neither has. To exclude non-people, filter on contactType rather than on this.","properties":{"present":{"type":"boolean","description":"True when an individual record exists for this entity."},"source":{"type":"string","description":"Always sdwis-tinindiv — the register this is read from, named so the claim can be checked."}},"required":["present","source"]},"jobTitle":{"type":["null","string"],"description":"Job title, where a person record exists."},"phone":{"type":["null","string"],"description":"Business phone. Published only for administrative contacts and owners; null for every other role, matching the agency's own disclosure choice."},"email":{"type":["null","string"],"description":"Business email, with the same restriction as phone."},"address":{"type":"object","description":"Mailing address, with the same restriction as phone.","properties":{"line1":{"type":["null","string"]},"line2":{"type":["null","string"]},"city":{"type":["null","string"]},"state":{"type":["null","string"]},"zip":{"type":["null","string"]}}},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}}}}},"required":["meta","page","pwsid","contacts"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/service-connections":{"get":{"summary":"Service connections","tags":["water systems"],"description":"How many connections the system serves, grouped by connection class and meter arrangement.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"serviceConnections":{"type":"array","items":{"type":"object","description":"Service connections of one type and meter arrangement.","properties":{"connectionType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Class of connection — residential, commercial and so on. Null when the underlying record has no value for this field."},"meterType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Metering arrangement. Null when the underlying record has no value for this field."},"connectionCount":{"type":["null","integer"],"description":"Number of connections in this group."},"meterSizeMeasure":{"type":["null","number"],"description":"The agency's meter size measure, as recorded. **The unit is not published by the agency and this API does not guess it.** The agency's own page heads it \"Meter Size Measure\", and no source establishes the unit. Do not label this value with a unit when you publish it.\n\nZero is published as zero, as the agency's page prints it. Null means the column is null, nothing more."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}}}}},"required":["meta","page","pwsid","serviceConnections"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/service-areas":{"get":{"summary":"Service areas","tags":["water systems"],"description":"The areas this system serves, as classified by the agency.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"serviceAreas":{"type":"array","items":{"type":"object","description":"An area the system serves.","properties":{"areaClass":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Kind of area served. Null when the underlying record has no value for this field."},"name":{"type":["null","string"],"description":"Area name."}}}}},"required":["meta","page","pwsid","serviceAreas"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/purchases":{"get":{"summary":"Water purchases","tags":["water systems"],"description":"Systems this one buys finished water from. An empty list means no purchase is on record — many systems produce all their own water.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"purchases":{"type":"array","items":{"type":"object","description":"Water bought from another public water system.","properties":{"sellerPwsid":{"type":["null","string"],"description":"PWSID of the selling system — follow it with GET /v1/water-systems/{pwsid}."},"sellerName":{"type":["null","string"],"description":"Name of the selling system."},"sellerHref":{"type":"string","description":"The selling system's record here. PRESENT ONLY when this site's own search shows that system; a seller it does not show is listed by PWSID and name, as the agency's page lists it, and not linked."},"sellerFacilityId":{"type":["null","string"],"description":"Facility the water leaves the seller through."},"sellerFacilityType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Type of that facility. Null when the underlying record has no value for this field."},"buyerFacilityId":{"type":["null","string"],"description":"Facility the water enters this system through."},"buyerFacilityType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Type of that facility. Null when the underlying record has no value for this field."}}}}},"required":["meta","page","pwsid","purchases"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/water-systems/{pwsid}/operating-periods":{"get":{"summary":"Operating periods and population served","tags":["water systems"],"description":"When the system operates each year and how many people it serves. This is where the population figure on the public page comes from — it is an agency-recorded service population, not a census count.","parameters":[{"schema":{"$ref":"#/components/schemas/Pwsid"},"in":"path","name":"pwsid","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"pwsid":{"type":"string","description":"The system these records belong to."},"dataQualityChecks":{"type":"string","enum":["checked","no-check-defined","not-checkable"],"description":"What an empty `dataQuality` on these rows means. \"checked\": a check runs over this collection, so an empty array is the result of looking; it does not promise the hazard occurs in this installation, only that something inspects for it. \"no-check-defined\": nothing inspects these rows, so an empty array is not evidence of anything. \"not-checkable\": no hazard on this record type is expressible as a check. **Absent means the question has not been answered for this resource, and must not be read as \"checked\".** It is omitted rather than defaulted, because a default would assert one of the three about a resource nobody has read. It is absent on /summary on purpose: that is a composite of blocks drawn from several record types rather than one collection of rows, so a single state over it would describe none of them. Read each block on the collection that owns it. An unrecognised value must also not be read as \"checked\"."},"reading":{"type":"string"},"window":{"type":"object","description":"This list is narrowed by default, by the agency's own rule, and these are the dates applied. `from`: rows whose date is on or before it are left out. `asOf`: rows are those in effect on this date. `reading` says which rule, in a sentence.","properties":{"from":{"type":"string","format":"date"},"asOf":{"type":"string","format":"date"},"source":{"type":"string"}},"required":["source"]},"operatingPeriods":{"type":"array","items":{"type":"object","description":"An annual operating period and the population served during it.","properties":{"startMonth":{"type":["null","integer"],"description":"Month the period starts, 1-12."},"startDay":{"type":["null","integer"],"description":"Day of month the period starts."},"endMonth":{"type":["null","integer"],"description":"Month the period ends, 1-12."},"endDay":{"type":["null","integer"],"description":"Day of month the period ends."},"populationType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Category of the population counted. Null when the underlying record has no value for this field."},"averageDailyPopulation":{"type":["null","integer"],"description":"Average daily population served during the period."}}}}},"required":["meta","page","pwsid","operatingPeriods"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/code-tables":{"get":{"summary":"List code domains","tags":["reference"],"description":"Every coded field in this API resolves against one of these domains. Fetch a domain to get its values, and read labelSource before repeating a label as the agency's own wording.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","description":"Domain name, as used in GET /v1/code-tables/{domain}."},"description":{"type":"string","description":"What this domain means."},"labelSource":{"type":"string","description":"Where the labels came from: this agency's own code table, a federal standard, or inference by this API."},"valueCount":{"type":"integer","description":"Number of codes in the domain."}},"required":["domain","description","labelSource","valueCount"]}}},"required":["meta","page","domains"]}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/code-tables/{domain}":{"get":{"summary":"One code domain","tags":["reference"],"description":"All codes in one domain, with their labels. Every value listed here HAS a label — the domain-level labelSource says where they all came from. Where it is \"curated-unverified\" no authoritative decode exists anywhere and the labels are this API's inference, so present them as such or present the bare code. Where it is \"dww-help\" the labels are quoted from the help topic named in helpTopic, which ships inside Drinking Water Watch and is linked from every records page — published text, so the quotation can be checked, but the application's wording rather than the agency's. helpTopicUrl is that topic's address, so checking it takes no knowledge of where this agency's application is deployed. This list is not exhaustive of what the data contains: a code that appears in a record but not here decodes to a null label marked \"no-decode-available\" at the point of use, which is a per-value field on the records themselves and not on this endpoint.","parameters":[{"schema":{"type":"string"},"in":"path","name":"domain","required":true,"description":"Domain name from GET /v1/code-tables."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"domain":{"type":"string"},"description":{"type":"string"},"labelSource":{"type":"string"},"helpTopic":{"type":"string","description":"Present only when labelSource is \"dww-help\": the help topic inside Drinking Water Watch these labels were quoted from, so the quotation can be checked rather than taken on trust. Absent once an installation's own code table supersedes it, because then the labels are no longer the ones in that file."},"helpTopicUrl":{"type":"string","description":"helpTopic resolved against the agency application named at coverage.publishedAt, so the topic can be opened without knowing where that application is deployed. Present whenever helpTopic is AND this installation has been told where its own Drinking Water Watch lives; absent means that address is not configured here, never that the topic is missing."},"installationCodeTable":{"type":"object","description":"Present when this domain is meant to be defined by the installation's own SDWIS code table, and says whether that table was found. labelSource says where the labels came from; this says where they were meant to come from. \"dww-help\" with no installationCodeTable is by design. The same labelSource with status \"absent-from-installation\" means an agency table was expected and this deployment has none, so the labels may be more general than the records.","properties":{"codeName":{"type":"string","description":"The name this domain has in the installation's own SDWIS code table."},"status":{"type":"string","enum":["read-from-installation","absent-from-installation","present-but-unusable","lookup-failed","not-loaded"],"description":"\"read-from-installation\" — the labels in `values` are this installation's. \"absent-from-installation\" — the code table is not in this deployment and the labels are built-in. \"present-but-unusable\" — it is there and yielded no usable code/description pair, which is a decision by the deployment rather than a gap. \"lookup-failed\" — the query errored. \"not-loaded\" — this process never looked, which should not happen in a served response and means the payload is untrustworthy for this domain."}},"required":["codeName","status"]},"codeGroups":{"type":"array","description":"Codes in this domain that mean the same thing under different names, where the domain has any. Present so that a question like \"when was this system last inspected for X?\" can be asked correctly: filtering on the single most common code silently undercounts wherever an agency also uses a variant. Membership is this API's classification of the agency's codes — see basis — narrowed to THIS installation's code table: only codes this installation publishes are listed. It says which codes name the same kind of event; it does NOT say whether the agency considers the differences between them meaningful, which is a question for the agency.","items":{"type":"object","properties":{"group":{"type":"string","description":"What the codes have in common."},"note":{"type":"string","description":"What membership means, and who decided it."},"codes":{"type":"array","items":{"type":"string"},"description":"The codes in this group that this installation's code table actually publishes. May be empty, which means none of them apply here — an empty group is a real answer, not a missing one."},"excluded":{"type":"array","description":"Codes that plainly look like members and are deliberately not, each with the reason. Published because the alternative is silence, and silence about an obvious near-miss is indistinguishable from having overlooked it. Disagree with a reason here and you have everything you need to group differently.","items":{"type":"object","properties":{"code":{"type":"string"},"reason":{"type":"string"}},"required":["code","reason"]}},"basis":{"type":"string","description":"Always \"curated-unverified\". The codes and their labels are the agency's; the decision that several of them name one thing is this API's reading of those labels, and no agency has ratified it. It is deliberately not the domain's labelSource, which can be \"sdwis-code-table\" while this stays our inference."}},"required":["group","note","codes","excluded","basis"]}},"values":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The code as stored in SDWIS."},"label":{"type":["null","string"],"description":"What it means, as a TERM — short enough to put in a table cell. Where the agency publishes a fuller explanation it is in definition, not here. NULL where the application defines the code but gives no single term for it — read definition, which says why."},"definition":{"type":"string","description":"The explanation the help topic gives for this code, present only where it gives one and only while labelSource is \"dww-help\". Prose, sometimes several sentences — read it, do not put it in a table cell. It is here rather than on every record because a definition repeated on each row is a cost paid per row for something that never varies."},"note":{"type":"string","description":"This API's own note on this code, and the one field here that is NOT the agency's or the application's. Present only where this service has something to say about its own handling of a code — a reading it declines to choose between, a decode it does not have. Unlike definition it does not depend on labelSource, because a fact about what THIS SERVICE does is true whatever the labels came from."}},"required":["code","label"]}}},"required":["meta","domain","description","labelSource","values"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/analytes":{"get":{"summary":"Every analyte sampled for anywhere in the installation","tags":["reference"],"description":"The agency's own Analyte List across every system it serves. A vocabulary of what is tested for, not a measurement of what was found: a row means an accepted result exists somewhere in the date window, and nothing more. Microbiological analytes are excluded here by the agency's own query and are NOT excluded from a single system's analyte list. \n\n**No dates means the last two years, not the whole record.** Called without collectedFrom or collectedTo, this returns only rows whose collection date falls in the last two years, the agency's own default on its matching page. So a count from an undated call is a two-year count, not the full history. window.source in the response says which window you got. At most five years may separate the two dates; a wider window is refused with the reason, and that limit is this API's, not the agency's.","parameters":[{"schema":{"type":"string"},"in":"query","name":"collectedFrom","required":false,"description":"Earliest collection date, yyyy-mm-dd. Omit both dates for the agency's two-year default."},{"schema":{"type":"string"},"in":"query","name":"collectedTo","required":false,"description":"Latest collection date, yyyy-mm-dd."},{"schema":{"type":"string"},"in":"query","name":"pwsid","required":false,"description":"Only systems whose PWSID CONTAINS this text, without regard to case — a substring, as the agency's own search matches it, so a partial id narrows rather than failing. For one known system, its own collection under /water-systems/{pwsid} is the faster question."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Only systems whose NAME contains this text, without regard to case. % and _ are ordinary characters here, not wildcards."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Only systems whose principal county served is this one, matched whole and without regard to case. Constrained: GET /v1/water-systems/search-options lists the counties this accepts."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Only systems of this federal type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists the values. Non-Public (NP) systems stay excluded whatever you pass."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Only systems whose primary source is this code, e.g. GW for ground water. Constrained: GET /v1/water-systems/search-options lists the values."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"window":{"type":"object","description":"The collection-date window these rows came from, including when you did not choose it. A window is ALWAYS applied, exactly as it always is on the agency's own page.","properties":{"collectedFrom":{"type":"string"},"collectedTo":{"type":"string"},"source":{"type":"string"},"limit":{"type":"object","description":"The window cap, as a value. A form can set its own `min` from `earliestAcceptable` instead of hardcoding a number. Present only where a cap exists; the per-system collections have none and carry no `limit` object.","properties":{"maxYears":{"type":"integer","description":"The most years that may separate the two dates. THE RULE, and it does not move with the request."},"earliestAcceptable":{"type":"string","description":"That rule applied to THIS response's upper bound (yyyy-mm-dd): the earliest lower bound this endpoint would accept alongside it. Computed here so a caller never does calendar arithmetic in days — 730.5 days is not two years."},"why":{"type":"string","description":"Whose limit it is and what it costs, including that one system's records carry no window limit at all."}},"required":["maxYears","earliestAcceptable","why"]}},"required":["collectedFrom","collectedTo"]},"filtersApplied":{"type":"object","description":"Which of the system criteria you sent actually did anything. Absent when you sent none, and that absence is the statement that this list is not narrowed by system. `applied` is what the statement was given; `ignored` names a criterion sent with no value, which is treated as not sent. A county, fedType or source this installation cannot match is not reported here: it is refused with a 400 listing the values that criterion accepts.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"}},"systemsMatched":{"type":"integer","description":"How many water systems your criteria select, before any record type, date window or toggle is applied. It tells the two causes of an empty result apart. 0 means no system matches what you sent; pwsid and name are free text, so a typo lands here. A positive figure beside total 0 means the matching systems have no record in this collection for this window. Present whenever at least one criterion was applied; absent when you sent none."},"ignored":{"type":"array","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"reading":{"type":"string"},"analytes":{"type":"array","items":{"type":"object","properties":{"code":{"type":["null","string"],"description":"The agency's analyte code. This is the value other endpoints' ?analyte= takes."},"name":{"type":["null","string"]},"type":{"type":["null","object"],"additionalProperties":true}},"required":["code","name","type"]}}},"required":["meta","page","window","reading","analytes"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/samples":{"get":{"summary":"Chem/rad samples at every system in the installation","tags":["sampling"],"description":"The agency's own chemical and radiological sample list across every system it serves, EXCEPT those it records as Non-Public — its own exclusion, applied on all of its all-systems pages, and some of the systems it removes are active. One row is a sample, not a result. The agency's page prints no analyte, value or unit on these rows, so neither does this. Follow system.records for a system's own samples, which carry their results inline. Microbiological analytes are excluded from the unfiltered list by the agency's own query — those samples are at /v1/coliform-samples, which is the other half of the same partition and the larger half here. Asking for a specific analyte is a different query: ?analyte= drops that exclusion and replaces it with three named codes (3100, 3013, 3014), so it PERMITS organisms the unfiltered list never shows. That is a difference between two of the agency's queries rather than a documented feature — its own page reaches this filter only through an analyte list that excludes organisms. Whether any are reachable depends on the installation and on your window; an empty page means no matching sample in that window, not that the analyte is unknown here. \n\n**No dates means the last two years, not the whole record.** Called without collectedFrom or collectedTo, this returns only rows whose collection date falls in the last two years, the agency's own default on its matching page. So a count from an undated call is a two-year count, not the full history. window.source in the response says which window you got. At most five years may separate the two dates; a wider window is refused with the reason, and that limit is this API's, not the agency's.\n\nPaged: 50 rows per request unless you ask for more with limit, at most 500. A larger limit is refused, not truncated. page.total counts every row that matched, not only the rows in this response; page.hasMore says whether to ask again with offset.","parameters":[{"schema":{"type":"string"},"in":"query","name":"collectedFrom","required":false,"description":"Earliest collection date, yyyy-mm-dd. Omit both dates for the agency's 2-year default. At most 5 years may separate the two — this endpoint's limit, not the agency's."},{"schema":{"type":"string"},"in":"query","name":"collectedTo","required":false,"description":"Latest collection date, yyyy-mm-dd."},{"schema":{"type":"string"},"in":"query","name":"pwsid","required":false,"description":"Only systems whose PWSID CONTAINS this text, without regard to case — a substring, as the agency's own search matches it, so a partial id narrows rather than failing. For one known system, its own collection under /water-systems/{pwsid} is the faster question."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Only systems whose NAME contains this text, without regard to case. % and _ are ordinary characters here, not wildcards."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Only systems whose principal county served is this one, matched whole and without regard to case. Constrained: GET /v1/water-systems/search-options lists the counties this accepts."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Only systems of this federal type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists the values. Non-Public (NP) systems stay excluded whatever you pass."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Only systems whose primary source is this code, e.g. GW for ground water. Constrained: GET /v1/water-systems/search-options lists the values."},{"schema":{"type":"string"},"in":"query","name":"analyte","required":false,"description":"One analyte code, as the agency records it. Switches this collection to the agency's by-analyte query, which applies a DIFFERENT microbiology exclusion — see the description. /v1/analytes lists the chem/rad codes with a result in a window and deliberately contains no organism, so an organism code must come from a system's own analyte list. An unknown code is a 400."},{"schema":{"type":"string"},"in":"query","name":"detectedOnly","required":false,"description":"Present for a detections-only reading: results with a recorded concentration above zero. That is the rule the agency's own query applies here, and NOT the less-than indicator that decides a detection on a single system's results. On or off: send it bare, empty, or =true to turn it on, and =false or leave it out to turn it off. Any other value, a repeated one included, is refused with a 400."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page, at most 500."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"window":{"type":"object","description":"The collection-date window these rows came from, including when you did not choose it. A window is ALWAYS applied, exactly as it always is on the agency's own page.","properties":{"collectedFrom":{"type":"string"},"collectedTo":{"type":"string"},"source":{"type":"string"},"limit":{"type":"object","description":"The window cap, as a value. A form can set its own `min` from `earliestAcceptable` instead of hardcoding a number. Present only where a cap exists; the per-system collections have none and carry no `limit` object.","properties":{"maxYears":{"type":"integer","description":"The most years that may separate the two dates. THE RULE, and it does not move with the request."},"earliestAcceptable":{"type":"string","description":"That rule applied to THIS response's upper bound (yyyy-mm-dd): the earliest lower bound this endpoint would accept alongside it. Computed here so a caller never does calendar arithmetic in days — 730.5 days is not two years."},"why":{"type":"string","description":"Whose limit it is and what it costs, including that one system's records carry no window limit at all."}},"required":["maxYears","earliestAcceptable","why"]}},"required":["collectedFrom","collectedTo"]},"filtersApplied":{"type":"object","description":"Which of the system criteria you sent actually did anything. Absent when you sent none, and that absence is the statement that this list is not narrowed by system. `applied` is what the statement was given; `ignored` names a criterion sent with no value, which is treated as not sent. A county, fedType or source this installation cannot match is not reported here: it is refused with a 400 listing the values that criterion accepts.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"}},"systemsMatched":{"type":"integer","description":"How many water systems your criteria select, before any record type, date window or toggle is applied. It tells the two causes of an empty result apart. 0 means no system matches what you sent; pwsid and name are free text, so a typo lands here. A positive figure beside total 0 means the matching systems have no record in this collection for this window. Present whenever at least one criterion was applied; absent when you sent none."},"ignored":{"type":"array","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"filters":{"type":"object","description":"What this page was narrowed by, echoed so a caller can tell an empty page caused by a filter from an empty collection.","properties":{"analyte":{"type":["null","object"],"description":"The analyte this page was filtered to, resolved to the agency's own code AND name, so a page with no rows can still say what it was looking for. Null when no filter was applied. An unknown code is a 400, not an empty page.","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"type":{"type":["null","object"],"additionalProperties":true},"excludedHere":{"type":"boolean","description":"True for the three codes the agency's own by-analyte query excludes by name. The page is empty by the agency's design rather than for want of records."}},"required":["code","name","type","excludedHere"]},"detectedOnly":{"type":"boolean"}},"required":["analyte","detectedOnly"]},"reading":{"type":"string"},"samples":{"type":"array","items":{"type":"object","properties":{"system":{"type":"object","properties":{"pwsid":{"type":["null","string"]},"name":{"type":["null","string"]},"countyServed":{"type":["null","string"]},"records":{"type":["null","string"]}},"required":["pwsid","name","countyServed","records"]},"labSampleNumber":{"type":["null","string"],"description":"The number the agency prints. NOT an address: it repeats within a system and some samples have none."},"sampleType":{"type":["null","object"],"additionalProperties":true},"collected":{"type":"object","properties":{"date":{"type":["null","string"]},"time":{"type":["null","string"]}},"required":["date","time"]},"samplingPoint":{"type":"object","properties":{"id":{"type":["null","string"]},"location":{"type":["null","string"],"description":"**The location may be withheld.** Where a point identifies a private dwelling this API publishes null and says so in dataQuality on the same object -- our decision, not an absence in the agency's records. The id, the type and every result taken there are unaffected."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["id","location"]},"laboratory":{"type":["null","string"]},"matchingSamples":{"type":["null","string"],"description":"The samples matching this row, in one call: the per-system samples collection filtered by the lab number, collection date and sampling point on this row. Named for what it returns — usually exactly this sample, and on a small fraction of rows the handful that share all three fields, because the agency records no unique identifier for a sample. Read `page.total` on the response for how many it matched; that count is computed over the records it actually ran against. Null only where the row publishes none of the three, since an unfiltered link would be the whole collection rather than this sample."}},"required":["system","labSampleNumber","sampleType","collected","samplingPoint","laboratory","matchingSamples"]}}},"required":["meta","page","window","filters","reading","samples"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/coliform-samples":{"get":{"summary":"Coliform (TCR) samples at every system in the installation","tags":["sampling"],"description":"The agency's own coliform sample list across every system it serves. One row is a SAMPLE with its results nested inside it; the agency prints one flat row per result and leaves \"was this sample positive\" as a join the reader performs. This collection and /v1/samples partition the agency's sample records between them on a single predicate — microbiological analytes here, everything else there — so neither is a subset of the other and an analyte absent from one is not absent from the agency. \n\n**No dates means the last two years, not the whole record.** Called without collectedFrom or collectedTo, this returns only rows whose collection date falls in the last two years, the agency's own default on its matching page. So a count from an undated call is a two-year count, not the full history. window.source in the response says which window you got. At most five years may separate the two dates; a wider window is refused with the reason, and that limit is this API's, not the agency's.\n\nPaged: 50 rows per request unless you ask for more with limit, at most 500. A larger limit is refused, not truncated. page.total counts every row that matched, not only the rows in this response; page.hasMore says whether to ask again with offset.","parameters":[{"schema":{"type":"string"},"in":"query","name":"collectedFrom","required":false,"description":"Earliest collection date, yyyy-mm-dd. Omit both dates for the agency's 2-year default. At most 5 years may separate the two — this endpoint's limit, not the agency's."},{"schema":{"type":"string"},"in":"query","name":"collectedTo","required":false,"description":"Latest collection date, yyyy-mm-dd."},{"schema":{"type":"string"},"in":"query","name":"pwsid","required":false,"description":"Only systems whose PWSID CONTAINS this text, without regard to case — a substring, as the agency's own search matches it, so a partial id narrows rather than failing. For one known system, its own collection under /water-systems/{pwsid} is the faster question."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Only systems whose NAME contains this text, without regard to case. % and _ are ordinary characters here, not wildcards."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Only systems whose principal county served is this one, matched whole and without regard to case. Constrained: GET /v1/water-systems/search-options lists the counties this accepts."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Only systems of this federal type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists the values. Non-Public (NP) systems stay excluded whatever you pass."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Only systems whose primary source is this code, e.g. GW for ground water. Constrained: GET /v1/water-systems/search-options lists the values."},{"schema":{"type":"string"},"in":"query","name":"positiveOnly","required":false,"description":"Present for the agency's own positives-only view. It narrows BOTH which samples appear AND which of their results are shown, exactly as the agency's page does. On or off: send it bare, empty, or =true to turn it on, and =false or leave it out to turn it off. Any other value, a repeated one included, is refused with a 400."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Samples per page, at most 500."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Samples to skip."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"window":{"type":"object","description":"The collection-date window these rows came from, including when you did not choose it. A window is ALWAYS applied, exactly as it always is on the agency's own page.","properties":{"collectedFrom":{"type":"string"},"collectedTo":{"type":"string"},"source":{"type":"string"},"limit":{"type":"object","description":"The window cap, as a value. A form can set its own `min` from `earliestAcceptable` instead of hardcoding a number. Present only where a cap exists; the per-system collections have none and carry no `limit` object.","properties":{"maxYears":{"type":"integer","description":"The most years that may separate the two dates. THE RULE, and it does not move with the request."},"earliestAcceptable":{"type":"string","description":"That rule applied to THIS response's upper bound (yyyy-mm-dd): the earliest lower bound this endpoint would accept alongside it. Computed here so a caller never does calendar arithmetic in days — 730.5 days is not two years."},"why":{"type":"string","description":"Whose limit it is and what it costs, including that one system's records carry no window limit at all."}},"required":["maxYears","earliestAcceptable","why"]}},"required":["collectedFrom","collectedTo"]},"filtersApplied":{"type":"object","description":"Which of the system criteria you sent actually did anything. Absent when you sent none, and that absence is the statement that this list is not narrowed by system. `applied` is what the statement was given; `ignored` names a criterion sent with no value, which is treated as not sent. A county, fedType or source this installation cannot match is not reported here: it is refused with a 400 listing the values that criterion accepts.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"}},"systemsMatched":{"type":"integer","description":"How many water systems your criteria select, before any record type, date window or toggle is applied. It tells the two causes of an empty result apart. 0 means no system matches what you sent; pwsid and name are free text, so a typo lands here. A positive figure beside total 0 means the matching systems have no record in this collection for this window. Present whenever at least one criterion was applied; absent when you sent none."},"ignored":{"type":"array","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"filters":{"type":"object","properties":{"positiveOnly":{"type":"boolean"}},"required":["positiveOnly"]},"reading":{"type":"string"},"samples":{"type":"array","items":{"type":"object","properties":{"system":{"type":"object","properties":{"pwsid":{"type":["null","string"]},"name":{"type":["null","string"]},"countyServed":{"type":["null","string"]},"records":{"type":["null","string"]}},"required":["pwsid","name","countyServed","records"]},"labSampleNumber":{"type":["null","string"]},"sampleType":{"type":["null","object"],"additionalProperties":true},"collected":{"type":"object","properties":{"date":{"type":["null","string"]},"time":{"type":["null","string"]}},"required":["date","time"]},"samplingPoint":{"type":"object","properties":{"id":{"type":["null","string"]},"location":{"type":["null","string"],"description":"**The location may be withheld.** Where a point identifies a private dwelling this API publishes null and says so in dataQuality on the same object -- our decision, not an absence in the agency's records. The id, the type and every result taken there are unaffected."},"dataQuality":{"$ref":"#/components/schemas/DataQuality"}},"required":["id","location"]},"laboratory":{"type":["null","string"]},"findings":{"type":"object","description":"Counts over the results SHOWN on this sample. Under positiveOnly that is the Present ones, not everything recorded.","additionalProperties":true},"results":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["system","labSampleNumber","sampleType","collected","samplingPoint","laboratory","findings","results"]}}},"required":["meta","page","window","filters","reading","samples"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/coliform-summaries":{"get":{"summary":"Coliform sample summaries at every system, by monitoring period","tags":["sampling"],"description":"The agency's own Coliform Sample Summary Results across every system it serves: how many routine coliform-negative samples it recorded for each monitoring period. The window is on the monitoring period, not on a collection date — a summary is a count over a period and has no collection date, so this endpoint takes periodFrom and periodTo where its siblings take collectedFrom and collectedTo. Copying a window from another all-systems URL will be refused rather than silently applied to a different column. The agency selects summaries carrying one of three coliform analyte codes, so an installation holding summaries of another kind shows an empty collection here and an empty page on the agency's own site. When that happens the payload reports what IS loaded, counted at request time, rather than guessing.\n\n**No dates means the last two years, not the whole record.** Called without periodFrom or periodTo, this returns only rows whose monitoring-period start date falls in the last two years, the agency's own default on its matching page. So a count from an undated call is a two-year count, not the full history. window.source in the response says which window you got. At most five years may separate the two dates; a wider window is refused with the reason, and that limit is this API's, not the agency's.\n\nPaged: 50 rows per request unless you ask for more with limit, at most 500. A larger limit is refused, not truncated. page.total counts every row that matched, not only the rows in this response; page.hasMore says whether to ask again with offset.","parameters":[{"schema":{"type":"string"},"in":"query","name":"periodFrom","required":false,"description":"Earliest monitoring-period START date, yyyy-mm-dd. Omit both dates for the agency's 2-year default. At most 5 years may separate the two — this endpoint's limit, not the agency's."},{"schema":{"type":"string"},"in":"query","name":"periodTo","required":false,"description":"Latest monitoring-period START date, yyyy-mm-dd."},{"schema":{"type":"string"},"in":"query","name":"pwsid","required":false,"description":"Only systems whose PWSID CONTAINS this text, without regard to case — a substring, as the agency's own search matches it, so a partial id narrows rather than failing. For one known system, its own collection under /water-systems/{pwsid} is the faster question."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Only systems whose NAME contains this text, without regard to case. % and _ are ordinary characters here, not wildcards."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Only systems whose principal county served is this one, matched whole and without regard to case. Constrained: GET /v1/water-systems/search-options lists the counties this accepts."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Only systems of this federal type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists the values. Non-Public (NP) systems stay excluded whatever you pass."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Only systems whose primary source is this code, e.g. GW for ground water. Constrained: GET /v1/water-systems/search-options lists the values."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page, at most 500."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"window":{"type":"object","description":"The MONITORING-PERIOD window these rows came from, including when you did not choose it. A window is ALWAYS applied.","properties":{"periodFrom":{"type":"string"},"periodTo":{"type":"string"},"source":{"type":"string"},"limit":{"type":"object","description":"The window cap, as a value. A form can set its own `min` from `earliestAcceptable` instead of hardcoding a number. Present only where a cap exists; the per-system collections have none and carry no `limit` object.","properties":{"maxYears":{"type":"integer","description":"The most years that may separate the two dates. THE RULE, and it does not move with the request."},"earliestAcceptable":{"type":"string","description":"That rule applied to THIS response's upper bound (yyyy-mm-dd): the earliest lower bound this endpoint would accept alongside it. Computed here so a caller never does calendar arithmetic in days — 730.5 days is not two years."},"why":{"type":"string","description":"Whose limit it is and what it costs, including that one system's records carry no window limit at all."}},"required":["maxYears","earliestAcceptable","why"]}},"required":["periodFrom","periodTo"]},"filtersApplied":{"type":"object","description":"Which of the system criteria you sent actually did anything. Absent when you sent none, and that absence is the statement that this list is not narrowed by system. `applied` is what the statement was given; `ignored` names a criterion sent with no value, which is treated as not sent. A county, fedType or source this installation cannot match is not reported here: it is refused with a 400 listing the values that criterion accepts.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"}},"systemsMatched":{"type":"integer","description":"How many water systems your criteria select, before any record type, date window or toggle is applied. It tells the two causes of an empty result apart. 0 means no system matches what you sent; pwsid and name are free text, so a typo lands here. A positive figure beside total 0 means the matching systems have no record in this collection for this window. Present whenever at least one criterion was applied; absent when you sent none."},"ignored":{"type":"array","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"reading":{"type":"string"},"summariesLoaded":{"type":["null","object"],"description":"Present only when this collection is empty: what sample summaries the installation actually holds, by analyte, so an empty page can be read as \"none loaded\" or \"loaded, of another kind\".","properties":{"note":{"type":"string"},"analytes":{"type":"array","items":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"summaries":{"type":"integer"}},"required":["code","name","summaries"]}}},"required":["note","analytes"]},"summaries":{"type":"array","items":{"type":"object","properties":{"system":{"type":"object","properties":{"pwsid":{"type":["null","string"]},"name":{"type":["null","string"]},"countyServed":{"type":["null","string"]},"records":{"type":["null","string"]}},"required":["pwsid","name","countyServed","records"]}},"additionalProperties":true,"required":["system"]}}},"required":["meta","page","window","reading","summariesLoaded","summaries"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/lead-and-copper":{"get":{"summary":"Lead and copper 90th-percentile summaries at every system","tags":["sampling"],"description":"The agency's own lead and copper sample summaries across every system it serves. **Read summaryType before reading measure.** The agency records more than one kind of summary per period, and this response carries all of them, as the agency's page does. So one system and substance can appear several times in a period with different numbers. Only summaryType.isNinetiethPercentile marks the 90th percentile — the figure the federal Lead and Copper Rule compares against an action level. Taking any measure here as a 90th percentile will overstate some systems. A system's own page shows only the 90th percentile, because the agency filters its per-system query to it. A summary is not a sample result — it is computed over the period. A value of zero is a recorded measurement, published as zero rather than as absent: it is the result at that rank, NOT a statement that nothing was detected in the period. The window is on the monitoring period, not a collection date: these records have none, so this endpoint takes periodFrom and periodTo. It reads the same two tables as /v1/coliform-summaries and the agency partitions them by analyte, so one being empty says nothing about the other.\n\n**No dates means the last two years, not the whole record.** Called without periodFrom or periodTo, this returns only rows whose monitoring-period start date falls in the last two years, the agency's own default on its matching page. So a count from an undated call is a two-year count, not the full history. window.source in the response says which window you got. At most five years may separate the two dates; a wider window is refused with the reason, and that limit is this API's, not the agency's.\n\nPaged: 50 rows per request unless you ask for more with limit, at most 500. A larger limit is refused, not truncated. page.total counts every row that matched, not only the rows in this response; page.hasMore says whether to ask again with offset.","parameters":[{"schema":{"type":"string"},"in":"query","name":"periodFrom","required":false,"description":"Earliest monitoring-period START date, yyyy-mm-dd. Omit both dates for the agency's 2-year default. At most 5 years may separate the two — this endpoint's limit, not the agency's."},{"schema":{"type":"string"},"in":"query","name":"periodTo","required":false,"description":"Latest monitoring-period START date, yyyy-mm-dd."},{"schema":{"type":"string"},"in":"query","name":"pwsid","required":false,"description":"Only systems whose PWSID CONTAINS this text, without regard to case — a substring, as the agency's own search matches it, so a partial id narrows rather than failing. For one known system, its own collection under /water-systems/{pwsid} is the faster question."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Only systems whose NAME contains this text, without regard to case. % and _ are ordinary characters here, not wildcards."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Only systems whose principal county served is this one, matched whole and without regard to case. Constrained: GET /v1/water-systems/search-options lists the counties this accepts."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Only systems of this federal type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists the values. Non-Public (NP) systems stay excluded whatever you pass."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Only systems whose primary source is this code, e.g. GW for ground water. Constrained: GET /v1/water-systems/search-options lists the values."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page, at most 500."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"window":{"type":"object","description":"The MONITORING-PERIOD window these rows came from, including when you did not choose it. A window is ALWAYS applied.","properties":{"periodFrom":{"type":"string"},"periodTo":{"type":"string"},"source":{"type":"string"},"limit":{"type":"object","description":"The window cap, as a value. A form can set its own `min` from `earliestAcceptable` instead of hardcoding a number. Present only where a cap exists; the per-system collections have none and carry no `limit` object.","properties":{"maxYears":{"type":"integer","description":"The most years that may separate the two dates. THE RULE, and it does not move with the request."},"earliestAcceptable":{"type":"string","description":"That rule applied to THIS response's upper bound (yyyy-mm-dd): the earliest lower bound this endpoint would accept alongside it. Computed here so a caller never does calendar arithmetic in days — 730.5 days is not two years."},"why":{"type":"string","description":"Whose limit it is and what it costs, including that one system's records carry no window limit at all."}},"required":["maxYears","earliestAcceptable","why"]}},"required":["periodFrom","periodTo"]},"filtersApplied":{"type":"object","description":"Which of the system criteria you sent actually did anything. Absent when you sent none, and that absence is the statement that this list is not narrowed by system. `applied` is what the statement was given; `ignored` names a criterion sent with no value, which is treated as not sent. A county, fedType or source this installation cannot match is not reported here: it is refused with a 400 listing the values that criterion accepts.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"}},"systemsMatched":{"type":"integer","description":"How many water systems your criteria select, before any record type, date window or toggle is applied. It tells the two causes of an empty result apart. 0 means no system matches what you sent; pwsid and name are free text, so a typo lands here. A positive figure beside total 0 means the matching systems have no record in this collection for this window. Present whenever at least one criterion was applied; absent when you sent none."},"ignored":{"type":"array","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"systemsWithNoPeriodInWindow":{"type":"object","description":"Present only when a system criterion was applied and the window left out some of the systems it matches. `count` is how many of the filtersApplied.systemsMatched systems have no summary for a monitoring period inside the window. eachSystem has every period for one system with no window. A disclosure of this request's window, not a fact about the systems.","properties":{"count":{"type":"integer"},"of":{"type":"integer"},"why":{"type":"string"},"eachSystem":{"type":"string"},"eachSystemNote":{"type":"string"}},"required":["count","of","why","eachSystem","eachSystemNote"]},"reading":{"type":"string"},"summaries":{"type":"array","items":{"type":"object","properties":{"system":{"type":"object","properties":{"pwsid":{"type":["null","string"]},"name":{"type":["null","string"]},"countyServed":{"type":["null","string"]},"records":{"type":["null","string"]}},"required":["pwsid","name","countyServed","records"]},"summaryType":{"type":["null","object"],"description":"The agency's own summary type code. Neither the agency's application nor its code tables decode these codes, so label is null and labelSource is no-decode-available: do not read AL as \"action level\" or any other name, because nothing published says so. Only isNinetiethPercentile marks the 90th percentile, as a test on the code.","properties":{"code":{"type":["null","string"]},"label":{"type":"null"},"labelSource":{"type":"string","enum":["no-decode-available"]},"isNinetiethPercentile":{"type":"boolean"}},"required":["code","label","labelSource","isNinetiethPercentile"]},"measure":{"type":"object","description":"The summarised value and the unit the agency recorded with it. Meaningless without summaryType.","properties":{"value":{"type":["null","number"]},"unit":{"type":["null","string"]}},"required":["value","unit"]},"sampleCount":{"type":["null","integer"]},"summaryReceivedDate":{"type":["null","string"],"format":"date","description":"The date the agency received the summary, where recorded."},"facilityId":{"type":["null","string"]},"resultQuality":{"type":["null","object"],"description":"The quality status of the summarised result, as the agency codes it.","properties":{"code":{"type":["null","string"]},"label":{"type":["null","string"]},"labelSource":{"type":"string"}}}},"additionalProperties":true,"required":["system","summaryType","measure"]}}},"required":["meta","page","window","reading","summaries"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/mdbp-summaries":{"get":{"summary":"Summarized field sample results (MDBP summaries) at every system","tags":["sampling"],"description":"The agency's own MDBP summaries across every system it serves, one row per monitoring period per analyte per facility. Each row is facility-keyed — the agency records these against a facility and its analyte level, so two rows of one system in one period are not duplicates, and the facility is published beside the system for that reason. The compliance indicators are the agency's own determination, published as it records them. The window is on the monitoring period, not a collection date: these records have none, so this collection takes periodFrom and periodTo.\n\n**No dates means the last two years, not the whole record.** Called without periodFrom or periodTo, this returns only rows whose monitoring-period start date falls in the last two years, the agency's own default on its matching page. So a count from an undated call is a two-year count, not the full history. window.source in the response says which window you got. At most five years may separate the two dates; a wider window is refused with the reason, and that limit is this API's, not the agency's.\n\nPaged: 50 rows per request unless you ask for more with limit, at most 500. A larger limit is refused, not truncated. page.total counts every row that matched, not only the rows in this response; page.hasMore says whether to ask again with offset.","parameters":[{"schema":{"type":"string"},"in":"query","name":"periodFrom","required":false,"description":"Earliest monitoring-period START date, yyyy-mm-dd. Omit both dates for the agency's 2-year default. At most 5 years may separate the two — this endpoint's limit, not the agency's."},{"schema":{"type":"string"},"in":"query","name":"periodTo","required":false,"description":"Latest monitoring-period START date, yyyy-mm-dd."},{"schema":{"type":"string"},"in":"query","name":"pwsid","required":false,"description":"Only systems whose PWSID CONTAINS this text, without regard to case — a substring, as the agency's own search matches it, so a partial id narrows rather than failing. For one known system, its own collection under /water-systems/{pwsid} is the faster question."},{"schema":{"type":"string"},"in":"query","name":"name","required":false,"description":"Only systems whose NAME contains this text, without regard to case. % and _ are ordinary characters here, not wildcards."},{"schema":{"type":"string"},"in":"query","name":"county","required":false,"description":"Only systems whose principal county served is this one, matched whole and without regard to case. Constrained: GET /v1/water-systems/search-options lists the counties this accepts."},{"schema":{"type":"string"},"in":"query","name":"fedType","required":false,"description":"Only systems of this federal type code, e.g. C for Community. Constrained: GET /water-systems/search-options lists the values. Non-Public (NP) systems stay excluded whatever you pass."},{"schema":{"type":"string"},"in":"query","name":"source","required":false,"description":"Only systems whose primary source is this code, e.g. GW for ground water. Constrained: GET /v1/water-systems/search-options lists the values."},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"in":"query","name":"limit","required":false,"description":"Rows per page, at most 500."},{"schema":{"type":"string"},"in":"query","name":"offset","required":false,"description":"Rows to skip."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"window":{"type":"object","description":"The MONITORING-PERIOD window these rows came from, including when you did not choose it. A window is ALWAYS applied.","properties":{"periodFrom":{"type":"string"},"periodTo":{"type":"string"},"source":{"type":"string"},"limit":{"type":"object","description":"The window cap, as a value. A form can set its own `min` from `earliestAcceptable` instead of hardcoding a number. Present only where a cap exists; the per-system collections have none and carry no `limit` object.","properties":{"maxYears":{"type":"integer","description":"The most years that may separate the two dates. THE RULE, and it does not move with the request."},"earliestAcceptable":{"type":"string","description":"That rule applied to THIS response's upper bound (yyyy-mm-dd): the earliest lower bound this endpoint would accept alongside it. Computed here so a caller never does calendar arithmetic in days — 730.5 days is not two years."},"why":{"type":"string","description":"Whose limit it is and what it costs, including that one system's records carry no window limit at all."}},"required":["maxYears","earliestAcceptable","why"]}},"required":["periodFrom","periodTo"]},"filtersApplied":{"type":"object","description":"Which of the system criteria you sent actually did anything. Absent when you sent none, and that absence is the statement that this list is not narrowed by system. `applied` is what the statement was given; `ignored` names a criterion sent with no value, which is treated as not sent. A county, fedType or source this installation cannot match is not reported here: it is refused with a 400 listing the values that criterion accepts.","properties":{"applied":{"type":"object","additionalProperties":{"type":"string"}},"systemsMatched":{"type":"integer","description":"How many water systems your criteria select, before any record type, date window or toggle is applied. It tells the two causes of an empty result apart. 0 means no system matches what you sent; pwsid and name are free text, so a typo lands here. A positive figure beside total 0 means the matching systems have no record in this collection for this window. Present whenever at least one criterion was applied; absent when you sent none."},"ignored":{"type":"array","items":{"type":"object","properties":{"parameter":{"type":"string"},"why":{"type":"string"},"effect":{"type":"string"}},"required":["parameter","why","effect"]}}},"required":["applied"]},"reading":{"type":"string"},"summaries":{"type":"array","items":{"type":"object","properties":{"system":{"type":"object","properties":{"pwsid":{"type":["null","string"]},"name":{"type":["null","string"]},"countyServed":{"type":["null","string"]},"records":{"type":["null","string"]}},"required":["pwsid","name","countyServed","records"]},"facility":{"type":"object","properties":{"id":{"type":["null","string"]},"records":{"type":["null","string"]}},"required":["id","records"]},"analyte":{"type":["null","object"],"properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}}},"summaryType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"What was measured and generally where — turbidity at the combined filter effluent, an entry-point residual disinfectant concentration, and so on. Null where the agency recorded none. Null when the underlying record has no value for this field.","type":["object","null"]},"monitoringPeriod":{"type":"object","properties":{"begins":{"type":["null","string"],"format":"date"},"ends":{"type":["null","string"],"format":"date"}},"required":["begins","ends"]},"samplesExceeded":{"type":["null","integer"],"description":"How many samples fell beyond or outside the level set in the facility analyte level for this analyte. Where the level is a MINIMUM this counts samples below it, so \"exceeded\" is the agency's word for either direction."},"percentExceeded":{"type":["null","string"],"description":"The same as a percentage, as the agency records it."},"periodAverage":{"type":"object","description":"The monitoring period average, as recorded. A number, from a NUMERIC column: 0 is a recorded value, null is not recorded.","properties":{"value":{"type":["null","number"]},"unit":{"type":["null","string"]}},"required":["value","unit"]},"runningAnnualAverage":{"type":"object","description":"The running annual average, as recorded. A number, from a NUMERIC column: 0 is a recorded value, null is not recorded.","properties":{"value":{"type":["null","number"]},"unit":{"type":["null","string"]}},"required":["value","unit"]},"monitoringCompliance":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the monitoring and reporting requirement was met, as the agency records it. THREE values, not two: one of them means a minor violation. Read the code. Null when the underlying record has no value for this field.","type":["object","null"]},"levelCompliance":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether the designated level was complied with, as the agency records it. Null when the underlying record has no value for this field.","type":["object","null"]},"assessedAgainst":{"type":"object","description":"The facility analyte level this summary was measured against — the same record the facility detail publishes under analyteLevels.","properties":{"controlLevelType":{"allOf":[{"$ref":"#/components/schemas/CodedValue"}],"description":"Whether that level is a maximum, minimum, average or 95th percentile. Null when the underlying record has no value for this field."},"measure":{"type":["null","string"],"description":"CHARACTER data, published verbatim."},"unit":{"type":["null","string"]}},"required":["controlLevelType","measure","unit"]}},"required":["system","facility","analyte","summaryType","monitoringPeriod","samplesExceeded","periodAverage","runningAnnualAverage","monitoringCompliance","levelCompliance","assessedAgainst"]}}},"required":["meta","page","window","reading","summaries"]}}}},"400":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/analyte-groups":{"get":{"summary":"Every analyte group the agency records","tags":["reference"],"description":"The agency's monitoring categories, with its own code and its own wording. Sampling schedules and violations are filed under a group, and the group stands for the analytes inside it. The name is not the membership — follow members for the list rather than reading a group's name as one. The agency publishes a group's members one group at a time and has no page listing the groups; this list is ours over its vocabulary.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"reading":{"type":"string"},"groups":{"type":"array","items":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"members":{"type":["null","string"]}},"required":["code","name","members"]}}},"required":["meta","page","reading","groups"]}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/v1/analyte-groups/{code}":{"get":{"summary":"The analytes in one analyte group","tags":["reference"],"description":"The analytes the agency records in this group — the list a schedule or violation filed under it stands for. Membership is the agency's record, not a definition of the category: it holds what the agency put in it, which need not match what the name suggests. An analyte can belong to more than one group. A group with no analytes recorded returns an empty list, not a 404 — the group existing and the group having members are different facts.","parameters":[{"schema":{"type":"string"},"in":"path","name":"code","required":true,"description":"The agency's own group code, as it appears on a schedule or a violation — NITR, VOC, PBCU, DBP. Listed at /v1/analyte-groups."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"meta":{"$ref":"#/components/schemas/Meta"},"page":{"$ref":"#/components/schemas/PageInfo"},"group":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]}},"required":["code","name"]},"reading":{"type":"string"},"analytes":{"type":"array","items":{"type":"object","properties":{"code":{"type":["null","string"]},"name":{"type":["null","string"]},"type":{"type":["null","object"],"additionalProperties":true}},"required":["code","name","type"]}}},"required":["meta","page","group","reading","analytes"]}}}},"404":{"description":"Error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request."}},"content":{"application/json":{"schema":{"description":"This installation allows 600 requests a minute from one address to `/v1`, an average of 10 a second. Over that, the answer is HTTP 429 with a `Retry-After` header giving the seconds to wait, and nothing else changes: a script that waits that long and continues loses nothing. `/health` reports the current setting as `requestLog.rateLimit`.","type":"object","properties":{"error":{"type":"string","enum":["rate-limited"]},"message":{"type":"string","description":"The limit, and how long to wait, in words."}},"required":["error","message"]}}}}}}},"/health":{"get":{"summary":"Liveness and configuration","tags":["reference"],"description":"Confirms the API is up, reports which database engine it is reading, and says how current that data is.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"engine":{"type":"string","description":"Database engine backing this instance."},"readOnly":{"type":"boolean","description":"Whether writes are refused. Always true."},"sourceId":{"type":"string","description":"Which api SOURCE built this response — a content hash of the api source tree this process was started from. COMPARE IT FOR EQUALITY AND NOTHING ELSE, exactly as with `instance`: same value means the same api source, different means not. It is not a version, does not increase, and says nothing about compatibility or which is newer. \"unknown\" means the tree could not be read and is NOT a value to compare — treat it as a failure to answer. It exists because a host that embeds this API in-process serves ITS OWN COPY while every page it renders advertises the JSON twin on this origin, so the pointer asserts an equivalence that nothing else can check."},"sourceState":{"type":"string","enum":["clean","dirty","unknown"],"description":"Whether the source `sourceId` names EXISTS IN HISTORY. \"clean\" means that source is committed, so a figure you stamp with this sourceId can be returned to and re-derived. \"dirty\" means it is a working state that was never committed: the sourceId still identifies it, so two responses can still be compared, but NOBODY CAN GO BACK TO IT — the moment that tree is edited again the source behind your measurement is gone, for us as much as for you. \"unknown\" means the check did not run and MUST NOT be read as clean. DATED AND RE-DERIVABLE ARE DIFFERENT PROPERTIES and sourceId alone gives only the first: a consumer recorded a set of measurements as reproducible on the strength of a hash, and they were taken against a tree that existed in no commit. Quote this beside sourceId or neither. AS OF PROCESS START — and the UI host's `uiSourceState` is NOT, which a consumer comparing the two hosts must know: it re-derives per request, so committing on both halves without restarting either leaves this saying \"dirty\" while that says \"clean\" with nothing about the two trees differing. Only the instruments differ: this one is computed once, at startup, and the UI's is re-derived per request. Neither is wrong about its own subject.\n\nCOMPUTED AT STARTUP, exactly as sourceId is, so the two can never disagree about their subject — and NEITHER DESCRIBES THE TREE ON DISK NOW, nor the code actually being served: a database statement is read from disk the first time this process needs it and may never have been in the tree that was hashed. See `instance`. That makes the two values asymmetric and only one of them can go stale: \"clean\" is permanent, because a source committed at boot stays reachable forever, while \"dirty\" may become PESSIMISTIC if that same source is committed later without restarting. It can never fail the other way. If you hold a \"dirty\" reading, the source may since have been committed; what you cannot do is assume it was."},"instance":{"type":"string","description":"Which process answered this request. COMPARE IT FOR EQUALITY AND NOTHING ELSE: two responses carrying the same value came from the same running process, and two carrying different values did not. It is a random token chosen at startup — it is NOT a version, a build identifier or a timestamp, it does not increase, and you cannot tell from two values which is newer or whether they are compatible.\n\nIT IS A PROCESS IDENTITY AND NEVER A BUILD IDENTITY, AND IT IS SILENT IN BOTH DIRECTIONS ABOUT THE CODE. A process restarts for reasons this service does not report, so a new value does not establish that anything about the code changed. And an unchanged value does not establish that it did not: a database statement is read from disk the first time this process needs it, which may be long after that process started, so TWO ANSWERS FROM ONE PROCESS CAN COME FROM DIFFERENT CODE. A consumer read a changed token as \"the code was redeployed\", wrote that in its own notes as a fact and reasoned onward from it; the token had licensed only the comparison, not the cause.\n\nSo if this service gives you two different answers to the same request, re-read rather than reconcile the two — that is the right move whether or not this value changed, and this field cannot tell you which of the two you are in.\n\nA DIFFERENCE WITH THIS VALUE UNCHANGED HAS MORE THAN ONE CAUSE: the data may have changed, your reading of it may differ, a statement may have been read from disk between the two answers, or THIS PROCESS MAY BE WARMING UP — a field computed lazily reads null until it is ready, so two answers seconds apart from one process can differ without anything changing. A null here always means not-yet-computed, never no-data, and that window reopens on every restart rather than once.\n\nsourceId answers a different and narrower question: what source tree was on disk when this process started. That is worth having and it is not \"were these two answers produced by the same code\" — no field here answers that one."},"recordingCadence":{"type":["null","array"],"description":"HOW OFTEN THIS INSTALLATION RECORDS, so that newestRecordChange can be read. One row per calendar year over the same column newestRecordChange comes from, newest year first.\n\nTHESE ARE OPERANDS, NOT A VERDICT. This service does not say whether it is serving stale data: it gives you the newest record it holds, and what a normal interval between recordings looks like here, and the comparison is yours. `longestGapDays` is the longest INTERVAL between two consecutive days on which anything was recorded — 1 means two consecutive days and no silence at all, 5 means four days passed with nothing written. Days-since-newestRecordChange is an interval too, so the two sides are directly comparable; a count of empty days would differ by one with nothing here to say which you held.\n\nNO WINDOW IS APPLIED AND THAT IS THE POINT. Reduce these rows over the last two years, the last ten, and all of them, and you will generally get three different longest intervals — so any single figure this service published would be a window WE chose, presented as a property of the data. Read the rows: they show what is normal here AND that it varies AND which years were unusual. A short-history installation shows fewer rows, which is itself worth knowing.\n\n`longestGapDays` is null for the earliest year, which has no preceding day to measure from. That is not zero, and zero would mean \"never silent\". NULL for the whole field means not yet computed on this process, never \"no history\".","items":{"type":"object","properties":{"year":{"type":"integer","description":"Calendar year, as the agency's own timestamps fall."},"recordingDays":{"type":"integer","description":"Distinct days in this year on which at least one record was written."},"longestGapDays":{"type":["null","integer"],"description":"Longest interval in days between consecutive recording days, for intervals ENDING in this year — the year a reader holding a recent date is asking about. 1 means consecutive days. Null on the earliest year only."}}}},"newestRecordChange":{"type":["null","string"],"description":"The most recent date on which ANY sample result in this installation was added or changed (ISO 8601). It is how you tell a current source from a stale copy: if this is far in the past, you are reading a mirror that has not been refreshed, and recent records will be missing however complete the response looks. It is NOT a load or import date — this API may be reading a live installation, where no such date exists — and it is not a promise that everything before it is present. NULL means not yet determined on this process, never that no data was found."},"productionGuard":{"type":"string","description":"How many database targets this installation refuses to start against (REFUSE_DB_TARGETS), in words. The targets themselves are not published. \"refusing none\" means no such guard is configured."},"requestLog":{"type":"object","description":"Whether requests to the data routes are being recorded, and whether the recorded keys can be matched across this API and the pages host. The raw address is never recorded; see `note`.","properties":{"collecting":{"type":"boolean"},"keysJoinAcrossProcesses":{"type":"boolean"},"writeFailures":{"type":"integer","description":"Records that could not be written since this process started."},"note":{"type":"string"},"clientAddress":{"type":"string","description":"Where this process takes the client address from, in words."},"rateLimit":{"type":"string","description":"The per-address request limit on the data routes, in words."}}}},"required":["status","engine","readOnly"]}}}}}}}},"servers":[{"url":"https://api.alpha.dww-next.mswater.us"}],"tags":[{"name":"water systems","description":"Public water systems and the records hung off them."},{"name":"reference","description":"Code tables and other lookups."},{"name":"discovery","description":"How a client that has never seen this service finds out what it is."}]}