Find a dog-friendly waterfront campsite that is actually bookable — across Connecticut, Massachusetts, Rhode Island, New York and Vermont state parks, Campspot- and Newbook-hosted private campgrounds, and recreation.gov.
It reads the real reservation systems rather than marketing pages, so everything it prints is something it saw in a live booking calendar.
python3 cli.py check --checkin 2026-09-04 --nights 3 --dogs --private Windmill Hill - Litchfield, CT - sites 01, 03, 05, 06, 09, 13, 15 [CT] (private, Campspot)
near water | 0.7h from New Haven
Tent Site | loop tent
$18/night, $53 for the stay | 8605670089
pets allowed (per booking record): yes
book: https://www.campspot.com/book/windmill-hill
This is a personal-use tool, published so you can run it yourself for your own trip. It is not a hosted service and should not be turned into one.
The booking sites it reads have terms that restrict automated access.
Aspira/ReserveAmerica prohibits using "any robot, bot, spider… or other manual
or automatic device or process to retrieve, index, data mine, scrape" its
sites, grants access "solely for your personal, non-commercial use," and sets
liquidated damages beyond 1,000 pages per 24 hours — a single full five-state
sweep is roughly 1,200–1,500 page loads, so don't run the unfiltered sweep
casually; use --lakes-only or --states. Campspot separately prohibits
scraping, copying its API, and reusing site data "even if not monetized."
Running this occasionally, from your own machine, to plan your own camping trip is the intended use. Running it as a public service, at volume, or from a datacenter is not — it would breach those terms, it would get blocked quickly (AWS WAF and DataDome score datacenter IPs aggressively), and wrong availability data sends real people on real drives. Decide for yourself where your use falls.
No credentials, tokens or API keys are included in this repository.
git clone https://github.com/grtwrn/campfinder.git
cd campfinder
python3 --version # 3.9+
pip install -r requirements.txt # only needed for the optional browser fallback
python3 cli.py check --checkin 2026-09-04 --nights 2 --dogs --lakes-onlyEverything except browser.py runs on the standard library alone.
Optional convenience:
alias campfinder="$PWD/campfinder"The repo ships a skill at .claude/skills/campfinder/SKILL.md. Clone it into a
project and Claude Code picks it up, or install it for every project:
mkdir -p ~/.claude/skills
cp -r .claude/skills/campfinder ~/.claude/skills/Then ask Claude things like "is anything open on Lake Waramaug this weekend that takes a dog?" and it will run the tool rather than guessing.
Set home in registry.json to your own coordinates. Coverage is strongest
in southern New England; the registry's lake list, waterfront ratings, drive
times and pet rules are all data you can edit without touching code.
cd campfinder
# What is open this weekend, dogs allowed, anywhere in CT/MA/RI/NY?
python3 cli.py check --checkin 2026-09-04 --nights 2 --dogs
# Only lakes, only sites the booking record flags as waterfront
python3 cli.py check --checkin 2026-09-04 --nights 2 --dogs --lakes-only --waterfront-only
# Everything sold out? Watch for cancellations and shout when one lands.
python3 cli.py watch --checkin 2026-09-04 --nights 2 --dogs --every 15m \
--notify 'notify-send "campsite open!"'--lakes-only restricts to the curated lake campgrounds in registry.json
(fast: ~16 facilities). Without it the scan covers all ~254 state facilities and
takes 20-30 minutes.
ReserveAmerica / Aspira (CT, MA, RI, NY state parks + NY DEC).
The modern www.reserveamerica.com React app pulls availability over XHR from
api.reserveamerica.com, which sits behind an AWS WAF and answers scripted
clients with a "Human Verification" page. Every state agency, though, also runs
a legacy server-rendered storefront on its own subdomain, and that one
renders the complete per-site / per-night matrix directly into the HTML — no
JavaScript, no bot wall:
| State | Storefront | Contract |
|---|---|---|
| CT | connecticutstateparks.reserveamerica.com |
CT |
| MA | massdcrcamping.reserveamerica.com |
MA |
| RI | rhodeislandstateparks.reserveamerica.com |
RI |
| NY | newyorkstateparks.reserveamerica.com |
NY |
The endpoint that matters:
/campsiteCalendar.do?page=matrix&contractCode=CT&parkId=100110&calarvdate=09/04/2026
calarvdate is the parameter that works (arvdate and lengthOfStay are
silently ignored). The response is a 2-week grid of <div class='td status a'>
cells — a available, r reserved, x not available.
recreation.gov has a clean public JSON API and needs none of this:
/api/camps/availability/campground/{id}/month?start_date=....
-
Session bleed. The storefront keeps the previous query's site-type filter in server-side session state. Reuse a session across parks and you silently get a filtered subset — 14 rows where the campground has 89 — with no error. Every query starts a fresh session.
-
Pagination. The site list is 25 rows per page. Reading only page 1 hides most of a big campground's sites, and therefore most of its openings.
-
Fake openings. Raw availability counts are actively misleading. On Labor Day weekend 2026, Myles Standish SF showed 12 "available" sites that were all flagged
HORSE REQUIRED TO CAMP; every Mount Greylock opening was aGROUP CAMPINGarea; Rutland and Pulaski's were day-useSHELTERS/PAVILIONS.is_real_campsite()filters these out — seeexcluded_site_typesinregistry.json.
campsiteDetails.do exposes per-site fields the listing pages don't:
Pets Allowed: Domestic Maximum Number of Pets: 2
Waterfront Site: N GIS Details Latitude: 41.377...
--dogs and --waterfront-only filter on these. Where an agency omits the
waterfront flag (MA does), a site is kept but marked unknown rather than
claimed as waterfront.
- Connecticut bans pets in every state PARK campground. Dogs are legal only at three state FOREST campgrounds: American Legion (Hawes), Pachaug (Green Falls, Mount Misery), and Salt Rock. Of those, only Green Falls is on still water — and its booking records show no waterfront sites.
- Massachusetts allows dogs at most DCR campgrounds, but a paper rabies certificate is required at check-in.
- New York allows 2 pets in designated loops; paper rabies certificate required (metal tags are not accepted).
Private campgrounds — which is where most genuinely on-the-water dog-friendly
sites near New Haven are (Point Folly on Bantam Lake, Laurel Lock on Gardner
Lake, Witch Meadow, Wilderness Lake, Odetah). They run on Campspot, RoverPass,
Firefly and CampLife, all of which sit behind DataDome or equivalent and expose
no availability to scripted clients. registry.json carries their names, lakes,
waterfront status, dog policies and phone numbers — for these, calling is
still the fastest path.
browser.py is a headless-Chromium CDP driver kept for that purpose. It works,
but it is slow on a Raspberry Pi and unreliable under load; treat it as a
fallback, not the main path.
Live at https://campfinder-planner.fly.dev — a small Flask site with real accounts, so you can keep a cookbook and build trip planners from a browser.
pip install -r requirements-web.txt
python3 -m web # http://localhost:8000Upload your cookbook once, reuse it forever. Paste recipes or upload
.md / .txt / .json / .csv. The parser keeps ingredients and throws away
the parts that would poison a grocery list — method steps, "Serves 4", prep
times — so you don't end up shopping for "Heat the oil in a large skillet".
Re-importing a recipe updates it in place.
Then name a meal on a day and the shopping list writes itself, using the same
plan.py derivation the CLI uses — so the site and the command line can't
drift. Export a trip as trip.json and the CLI takes over from there.
The site does not search campsite availability. Those booking systems'
terms permit personal, non-commercial use only; running that from a shared
host would breach them and get blocked besides. The Dockerfile doesn't even
copy reserveamerica.py, campspot.py, newbook.py or browser.py into the
image, so it's structural rather than a promise. Availability search stays a
local CLI, on your own machine, at your own volume.
Everything the site does host — recipes, menus, trips — is your own data.
fly apps create <your-app-name>
fly secrets set SECRET_KEY="$(python3 -c 'import secrets;print(secrets.token_urlsafe(48))')"
fly volumes create camp_data --region <region> --size 1
fly deployfly.toml mounts a volume at /data for the SQLite file. Run exactly one
machine — SQLite on a single volume cannot be shared, so don't scale
horizontally without moving to Postgres first.
Security notes, since it holds passwords: scrypt hashing with a per-user salt
(n=2^14, ~100ms/hash, maxmem set explicitly because OpenSSL's default 32 MB
cap otherwise throws rather than degrading), server-side session tokens so
logout genuinely revokes, CSRF tokens on every POST, per-IP login rate
limiting, and CSP/frame/nosniff headers.
| File | Purpose |
|---|---|
cli.py |
check / watch commands, filtering, output |
reserveamerica.py |
legacy storefront client, matrix parser, site details |
recreation_gov.py |
federal JSON API client |
registry.json |
lakes, waterfront status, pet rules, private-park phone list |
sweep.py |
bulk multi-state audit (writes JSON) |
browser.py |
headless Chromium over CDP (fallback for JS-only sites) |
plan.py |
trip planner — builds the 5-tab .xlsx (standalone) |
trips/ |
trip files: template.json and a full worked example |
web/ |
the hosted planner site (Flask + SQLite) |
Dockerfile, fly.toml |
deployment for the site only |
| Engine | Parks near New Haven | Status |
|---|---|---|
Campspot (campspot.py) |
Point Folly, Water's Edge, Chamberlain Lake, Windmill Hill, Prospect Mountain, Hopeville Hideaway, Strawberry Park, Lone Oak, White Pines, Sun Outdoors Mystic, ~70 more in 100 mi | Automated. www.campspot.com/api/gator-core with X-Client-Type: CONSUMER. Map search discovers parks; per-park call returns open site names + price + pet flag. guests = one count per age category in the park's order + pets - put adults in the slot named "adult". |
Newbook (newbook.py) |
Odetah | Automated. POST availability_chart_responsive to the instance's generated_api_files/<hash>.php. |
| Hipcamp | Quentins Treehouse, Sylvan Lake, Beach Pond | Not solved - GraphQL 404s to scripted calls; listing HTML ignores dates. |
| none (phone) | Witch Meadow, Laurel Lock, Wilderness Lake, Markham Meadows, Laurel Ridge, Wilcox | registry.json → phone_only. |
python3 cli.py check --checkin 2026-09-04 --nights 3 --dogs --private # state + private
python3 cli.py check --checkin 2026-09-04 --nights 3 --dogs --private --no-state --radius 70
python3 cli.py watch --checkin 2026-09-04 --nights 2 --dogs --private --every 15mHoliday rule: most private CT parks enforce a 3-night minimum on Labor Day / Memorial Day / July 4. A 2-night Fri→Sun query returns nothing while Fri→Mon is wide open. Query both.
A separate, additive tool: turns a trip description into a 5-tab planner —
Itinerary · Packing List · Prep Schedule · Menu · Shopping List — as an
.xlsx that imports straight into Google Sheets.
pip install openpyxl
python3 plan.py trips/example-white-mountains.json -o planner.xlsxThen in Sheets: File > Import > Upload > Replace spreadsheet, and select the
have / packed / Done? columns and Insert > Checkbox.
Three of the five tabs are derived rather than typed:
- Shopping list from the menu. Name a meal on a day, define its
ingredients once, and each one is routed to a supermarket aisle and tagged
with every meal that needs it —
Garlicbecomes a single row reading "Shrimp Scampi (Sat 8/22), T-bone Steak (Sun 8/23), Cold sesame noodle salad" instead of three separate rows. - Prep schedule from the departure date, counted backwards.
- Packing list from flags —
dog(personalised by name, and it remembers the rabies certificate),water,hiking,cold.
Details that stop it producing a subtly wrong list:
| Behaviour | Why |
|---|---|
| Recipes the menu never names are still shopped for | how "Breakfasts (all days)" and spare meals work — arriving with no eggs is the failure mode |
| Ingredients merge on a normalised name | "Garlic cloves (4-5)" → Garlic, "Unsalted butter (4-5 tbsp)" → Butter; but "Rice" and "Rice vinegar" stay separate |
| Aisle routing prefers the longest keyword match | otherwise PRODUCE claims "pepper" and "Red pepper flakes" ends up next to the onions |
Unrecognised items go to an OTHER aisle |
nothing is silently dropped |
Seed the itinerary from a campfinder search:
python3 cli.py check --checkin 2026-09-04 --nights 3 --dogs --private --json hits.json
python3 plan.py trips/mytrip.json --from-campfinder hits.jsonEach hit becomes a leg with its site number and booking URL, marked NOT YET BOOKED.
plan.py imports nothing from campfinder and campfinder imports nothing from
it — --from-campfinder just reads the JSON cli.py --json already writes.
Start from trips/template.json, or trips/example-white-mountains.json for a
full worked trip. Ships a Claude Code skill at .claude/skills/camp-planner/.
Vermont runs the same Aspira/ReserveAmerica storefront on its own domain —
vtstateparks-visit.com, contractCode=VT — so the existing client works
unchanged once the host is registered. 49 facilities.
python3 cli.py check --checkin 2026-09-04 --nights 2 --dogs --states VTVT allows pets at tent/RV and lean-to sites (rabies certificate required; not
in cabins/cottages). Its site records confirm Pets Allowed: Domestic, but —
unlike RI — carry no waterfront flag, so waterfront ratings for VT parks in
registry.json come from park knowledge, not the booking record.
Nothing in Vermont is under about 2.5 h from New Haven.
"Clean lake" is a per-trip question in September. Vermont publishes the only machine-readable live feed of the five states — the Health Department's Cyanobacteria Tracker, an ArcGIS Experience app over a public feature service:
services.arcgis.com/YKJ5JtnaPQ2jDbX8/arcgis/rest/services
/Cyanobacteria_All_Reports_view/FeatureServer/0/query
cli.py annotates every hit whose lake appears in the feed and sorts lakes
under an alert to the bottom. This is not cosmetic: on 2026-09-02 the biggest
block of open VT sites was 44 sites at Lake Carmi, which was under a Low
Alert dated 2026-08-31, while Woodford's Adams Reservoir read Generally
Safe.
A lake absent from the feed is unmonitored, not clean. CT/MA/RI/NY publish
advisories as human-readable pages only — see ADVISORY_PAGES in water.py.