# uk-bathing-water (`sunlit_hubbub/uk-bathing-water`) Actor

- **URL**: https://apify.com/sunlit_hubbub/uk-bathing-water.md
- **Developed by:** [josh hardy](https://apify.com/sunlit_hubbub) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 site results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## UK Bathing Water & River Conditions

Get the official status of every designated bathing water in England in one run: about 460 beaches, lakes and river swimming spots. Each site comes back as one clean row with:

- its **annual water quality rating** (Excellent / Good / Sufficient / Poor) from the Environment Agency,
- the **daily pollution-risk forecast** (in season),
- any **open pollution incident** or **suspension** of sampling,
- the **nearest live river gauge** within 3 km, with its typical range, so you can see if a river is running high,
- optionally, the **E. coli and intestinal enterococci counts** from the latest sample,
- a short list of **warning-sign codes**, worked out from the fields above.

The data comes from the Environment Agency's Bathing Water Quality and Flood Monitoring APIs. The raw APIs are linked data that is awkward to use: values are wrapped in nested objects, the sample year and date are hidden inside URIs, and river gauges aren't linked to bathing sites at all. This actor does that cleaning and joining for you.

> **This is data, not health advice.** It reports official ratings, forecasts and warning signs. It never says whether it is "safe" to swim. Always check signage at the site and the Environment Agency's own pages before going in the water.

### What you can use it for

- Travel, holiday and outdoor apps showing beach and lake conditions
- Open-water swimming, surf, coasteering, paddling and angling clubs and schools
- Local news and data journalism (for example "which beaches near us are rated Poor?")
- Research into water quality by region, water company or site type
- Daily alerts, using a scheduled run (see below)

### Input

All fields are optional. With no input, you get every site in England.

| Field | Type | What it does |
|---|---|---|
| `sites` | list of strings | Bathing water IDs (`ukj3308-19001`) or names. A name matches any site whose name contains it, so `windermere` returns all four Windermere sites. |
| `area` | object | `{"lat": 50.72, "lon": -1.88, "radiusKm": 15}` returns only sites within the radius, nearest first, and adds `distanceKm`. |
| `riverLevels` | `inland` / `all` / `none` | Which sites get the nearest river gauge. The default is `inland` (river and lake sites). |
| `includeLatestSample` | boolean | Adds the latest E. coli and enterococci counts. For more than 40 sites they come from one bulk list (refreshed every 6 hours), otherwise from one request per site. |
| `maxSites` | integer | Stop after this many sites. |
| `proxyConfiguration` | proxy | Defaults to an Apify **Residential** proxy in the **United Kingdom**. The Environment Agency blocks requests from outside the UK, including Apify's own servers. Each source is tried directly first, and the proxy is only used for sources that block that. |

Example: sites within 40 km of Reading, with river gauges for every site and the latest sample counts:

```json
{
  "area": { "lat": 51.48, "lon": -0.6, "radiusKm": 40 },
  "riverLevels": "all",
  "includeLatestSample": true
}
```

### Output

One dataset item per bathing water. This is a real row from 3 November 2026:

```json
{
  "id": "ukj1102-11941",
  "name": "Pangbourne Meadow, River Thames",
  "district": "Pangbourne Parish",
  "eaArea": "South East",
  "waterCompany": "Thames Water Utilities Limited",
  "lat": 51.48682521935274,
  "lon": -1.083940641648369,
  "type": "river",
  "isInland": true,
  "impactedByHeavyRain": false,
  "classification": null,
  "classificationYear": null,
  "riskLevel": null,
  "riskExpires": null,
  "suspension": null,
  "openIncident": null,
  "latestSampleDate": "2026-09-23",
  "eaUri": "http://environment.data.gov.uk/id/bathing-water/ukj1102-11941",
  "distanceKm": 33.52,
  "nation": "england",
  "inSeason": false,
  "riskForecastCurrent": false,
  "latestSample": {
    "sampledAt": "2026-09-23T09:00:00",
    "eColi": 150,
    "eColiQualifier": "=",
    "enterococci": 45,
    "enterococciQualifier": "="
  },
  "river": {
    "station": "Pangbourne",
    "stationRef": "2190TH",
    "river": "River Pang",
    "valueM": 0.141,
    "typicalLowM": 0.079,
    "typicalHighM": 0.78,
    "state": "normal",
    "readAt": "2026-11-03T12:15:00Z",
    "distanceKm": 0.34
  },
  "warningSigns": [],
  "source": "Environment Agency, Open Government Licence v3.0",
  "fetchedAt": "2026-11-03T12:42:35+00:00"
}
```

Pangbourne Meadow was first designated in 2025, so it has no annual rating yet, which is why `classification` is `null`.

#### Field notes

- **`type`**: `coastal`, `estuary`, `river` or `lake`, taken from the EA's own site type. `isInland` is true for rivers and lakes.
- **`classification`**: the latest annual rating: `Excellent`, `Good`, `Sufficient`, `Poor` or `Closed`. It is `null` for newly designated sites. `classificationYear` is the year it covers.
- **`riskLevel`**: the EA's daily pollution-risk forecast, `normal` or `increased`. The last forecast stays in the API after the season ends, so check `riskForecastCurrent`, which is only true while the forecast hasn't expired.
- **`suspension`**: the EA's latest active suspension of sampling, if any, with the start, expected end and reason exactly as the EA records them.
- **`openIncident`**: an open pollution incident recorded by the EA at the site, with its type and start time.
- **`dataAsOf`**: when the bathing water data (ratings, forecasts, incidents) was fetched from the Environment Agency. Their API is sometimes unavailable. When it is, the run uses the last good copy from the past 7 days instead of failing, says so in its status message and marks `OUTPUT` as stale. River levels are always live.
- **`latestSampleDate`** is always included. `latestSample` (the counts in cfu/100 ml, with `=`, `<` or `>` qualifiers) is only filled in when `includeLatestSample` is on.
- **`river`**: the nearest EA river gauge within 3 km that has a live reading. Gauges with a published typical range are preferred. `state` is `low`, `normal` or `high` against that range, and `null` if the gauge has no typical range. Levels are metres above the gauge's own datum, so compare them with the typical range, not with other gauges.

#### Warning-sign codes

| Code | When it appears |
|---|---|
| `pollution_risk_increased` | The EA forecasts an increased risk of pollution today. Only in season, and only while the forecast is current. |
| `pollution_incident_open` | The EA has an open pollution incident recorded at the site. |
| `rating_poor` | The latest annual rating is Poor. |
| `river_high` | The nearest river gauge is above its typical high level. |

An empty list means none of these official flags are raised. It does **not** mean the water is clean or safe.

### The bathing season

In England the season runs from **15 May to 30 September**. During the season:

- the EA samples each site, usually about weekly;
- it publishes **daily pollution-risk forecasts** at the sites where it forecasts.

Outside the season there are no new samples or forecasts. Ratings, incidents and river levels still update, and `inSeason` and `riskForecastCurrent` will be `false`.

### Scheduled daily runs

Forecasts are published each morning in season, and river gauges update every 15 minutes. A typical setup is a **daily schedule** (for example 10:00 UK time) with your sites or area saved as input, plus a webhook or integration that reads the dataset. Run it every few hours if you mainly want river levels.

### Run summary

Every run saves a summary to the `OUTPUT` record in the run's key-value store. It shows whether each source worked, how many sites matched and were returned, and whether the run stopped at your cost limit. If the river-level service is down, the run still returns every site, with `river: null`, and the summary records the error.

### Coverage and limits

- **England only** for now (about 460 designated bathing waters). Wales and Scotland are planned.
- River gauges are matched by distance (3 km or less). About 35 of the 38 inland sites currently have a gauge. A gauge may be on a tributary or a nearby stream rather than the bathing water itself, so check `river.river` and `river.distanceKm`.
- Sample counts and forecasts are only as fresh as the EA's own publishing.

### Data sources and licence

- Environment Agency **Bathing Water Quality** API: `environment.data.gov.uk/bwq`
- Environment Agency **Real-time flood-monitoring** API: `environment.data.gov.uk/flood-monitoring`

Both are published under the **Open Government Licence v3.0**. Contains public sector information licensed under the Open Government Licence v3.0. This actor isn't affiliated with or endorsed by the Environment Agency.

### Pricing

You pay per site returned (see the Pricing tab). A run that matches no sites costs nothing and says so in its status message. Turning on `includeLatestSample` or river levels doesn't change the price per site.

# Actor input Schema

## `sites` (type: `array`):

Bathing water IDs (e.g. ukj3308-19001) or names. A name matches any site whose name contains it, case-insensitive ("windermere" returns all Windermere sites). Leave empty for all sites.

## `area` (type: `object`):

Only sites within radiusKm of a point, nearest first, e.g. {"lat": 50.72, "lon": -1.88, "radiusKm": 15}. Combines with Sites if both are set.

## `riverLevels` (type: `string`):

Attach the nearest live Environment Agency river gauge (within 3 km) with its typical range. 'Inland only' covers river and lake sites.

## `includeLatestSample` (type: `boolean`):

Add E. coli and intestinal enterococci counts from each site's most recent sample. Makes one extra request per site, so runs over all sites take longer.

## `maxSites` (type: `integer`):

Stop after this many sites. Leave empty for no limit.

## `proxyConfiguration` (type: `object`):

The Environment Agency blocks requests from outside the UK, including Apify's own servers. The actor tries each source directly first and falls back to this proxy only for sources that block it. Keep a United Kingdom proxy here; Apify's datacentre proxies are US-only, so this needs the Residential group.

## Actor input object example

```json
{
  "sites": [
    "Windermere",
    "Spittal"
  ],
  "riverLevels": "inland",
  "includeLatestSample": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "GB"
  }
}
```

# Actor output Schema

## `sites` (type: `string`):

One item per bathing water: id, name, type, district, classification, riskLevel, riskForecastCurrent, openIncident, suspension, latestSample, river, warningSigns, dataAsOf, fetchedAt.

## `summary` (type: `string`):

The OUTPUT record: whether each source worked, sites matched and returned, whether stale bathing water data was used, and whether the run stopped at the cost limit.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sites": [
        "Windermere",
        "Spittal"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sunlit_hubbub/uk-bathing-water").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "sites": [
        "Windermere",
        "Spittal",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sunlit_hubbub/uk-bathing-water").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sites": [
    "Windermere",
    "Spittal"
  ]
}' |
apify call sunlit_hubbub/uk-bathing-water --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sunlit_hubbub/uk-bathing-water"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jxhC128JxKMcbvXUf/builds/YDwwgdQzdlKOhozaO/openapi.json
