Geocode a user's location query and return up to 40 nearby campsites from freecampsites.net (name, lat/lon, distance, fee tier, ratings, excerpt, detail-page URL, activity/amenity bitmasks) via the undocumented androidApp.php JSON endpoint, with suggest.php geocoding and an in-browser XHR fallback.
Works with
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionbrowse-campsitesExecute the skills CLI command in your project's root directory to begin installation:
Fetches browse-campsites from freecampsites.net/browse-campsites-qyj28m and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate browse-campsites. Access via /browse-campsites in your agent's command palette.
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Submit your Claude Code skill and start earning
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
0
total installs
0
this week
0
upvotes
Run in your terminal
0
installs
0
this week
—
stars
| name | browse-campsites |
| title | Free Campsites — Browse by Location |
| description | >- Geocode a user's location query and return up to 40 nearby campsites from freecampsites.net (name, lat/lon, distance, fee tier, ratings, excerpt, detail-page URL, activity/amenity bitmasks) via the undocumented androidApp.php JSON endpoint, with suggest.php geocoding and an in-browser XHR fallback. |
| website | freecampsites.net |
| category | travel |
| tags | - camping - outdoors - travel - free-camping - rv - boondocking - trip-planning |
| source | 'browserbase: agent-runtime 2026-05-19' |
| updated | '2026-05-19' |
| recommended_method | api |
| alternative_methods | - method: browser rationale: >- Use when --proxies is exhausted, the proxy IP gets rate-limited (21-byte empty-body responses persist for >3 retries), or you need server-side geocoding behavior that only works inside a browser session (cookies / referer). A bare `browse cloud sessions create --keep-alive` session opening https://freecampsites.net/ and firing the same XHR via `browse eval` is the most reliable transport observed. - method: hybrid rationale: >- Geocode via suggest.php (browser-originated XHR is most reliable; --proxies fetch is intermittent), then call androidApp.php with location=lat,lon — the lat/lon form bypasses the server-side name-resolver's flakiness on 'City, ST' queries. |
| verified | false |
| proxies | true |
Return a list of free (and paid / permit-required) campsites near a given location on freecampsites.net — for each result: name, latitude/longitude, distance from search center, fee tier, average star rating + vote count, short excerpt, canonical detail-page URL, and amenity/activity bitmask. Read-only; never edits site data, never posts reviews. The site is a WordPress + Leaflet map app with an undocumented JSON API (androidApp.php) that the web UI calls — this skill goes straight to the API.
The freecampsites.net web UI (the search box + map + side-panel listing) is a thin client over an undocumented androidApp.php JSON endpoint. There is no auth, no API key, no anti-bot wall — but residential proxies are mandatory when calling the endpoint from a Browserbase data-center IP (data-center IPs return HTTP 500). The endpoint geocodes internally but its name-resolver is flaky for "City, ST" (state-abbreviation) forms — geocode first with suggest.php, then search by lat,lon. Lead with the two-call API path; the browser flow is a backup only when proxies are exhausted or the proxy IP gets rate-limited (see the intermittent-empty gotcha below).
Geocode the user's location via suggest.php. Browserbase Fetch with --proxies works from a non-browser context; alternatively fire an XHR from inside a browse open https://freecampsites.net/ session (no proxy needed when the call is browser-originated).
GET https://freecampsites.net/wp-content/themes/freecampsites/suggest.php
?q={url-encoded user query}
&limit=5
Returns a GeoJSON FeatureCollection:
{
"type": "FeatureCollection",
"features": [
{
"geometry": {"coordinates": ["-111.7609900", "34.8697400"], "type": "Point"},
"id": "geo5313667",
"properties": {
"name": "Sedona", "state": "Arizona", "stateAbbr": "AZ",
"country": "United States", "population": 10388,
"access_counter": 12425
}
}
]
}
Coordinates are [lon, lat] (GeoJSON convention) — the standard GeoJSON order is longitude first, then latitude. Don't swap them. Pick features[0] as the canonical hit. If features is empty or the response body is empty, the location is unresolvable — surface the user-facing query unchanged and return success: false.
Search for campsites at that lat/lon via androidApp.php. Use --proxies (mandatory from data-center IPs) and write to file with --output — the in-band content field returned by Browserbase Fetch is unreliable for this response (often truncates to the leading whitespace preamble; the file output is the source of truth).
GET https://freecampsites.net/wp-content/themes/freecampsites/androidApp.php
?location={url-encoded "lat,lon"}
&coordinates={url-encoded "lat,lon"}
&advancedSearch=%7B%7D
&max_sites=40
Both location and coordinates are required — they accept either a place name (e.g. "Sedona, Arizona") or a lat/lon string (e.g. "34.8697,-111.7610"). Lat/lon is the reliable form because the server-side name-resolver fails silently for ambiguous 2-part "City, ST" queries (see gotcha below). advancedSearch={} is the empty-filter sentinel. max_sites=40 doubles the result count from the default 20 (the JS sets it whenever any toggle is active; the server caps higher values at 40 — max_sites=100 still returns 40). Server response (after trimming the ~23-byte WordPress whitespace preamble):
{
"latitude": "34.8648",
"longitude": "-111.81",
"queryspeed": "0.36ms",
"type": "campsite",
"default_icon": "...star.png",
"search_icon": "...star.png",
"resultList": [ /* up to max_sites entries */ ],
"metadata": {"created":"...","reviewed":"...","modified":"..."},
"city": "Sedona",
"region": "Arizona"
}
Decode each resultList[i] into the output shape:
id — int. Canonical detail-page URL: https://freecampsites.net/#!{id}&query=sitedetails (the slug-style url field works too but is SEO-flavored and may 301).name, latitude, longitude, distance (miles from search center, integer), city, county, region, country, excerpt.ratings_average (float 0–5), ratings_count (int — votes), ratings_value (sum of votes, internal).type_specific.fee — one of "Free", "Pay", "Pass/Permit Required" (matched by type_specific.icon color: green = Free, blue = Pass/Permit, red = Pay).type_specific.activities — bitmask integer, not a list. Known bits: 1=OHV, 2=Biking, 4=?, 8=?, 16=Hiking, 32=Horse Trails, 64=?, 128=?, 256=?, 512=?, 1024=Wildlife Viewing. Higher bits exist; decode by OR-checking against the constants you've identified, or fall back to the table_row HTML which has <img class="act-amen-icon act-{N}" alt="{Activity Name}"> for each bit set.type_specific.amenities — bitmask integer (often empty string "" when none). Same approach: decode via table_row's class="act-amen-icon" images with act- prefix.rating and table_row HTML strings unless you specifically need rendered markup — the structured fields above contain everything.(Optional) Fetch full detail for one campsite by id:
GET https://freecampsites.net/wp-content/themes/freecampsites/androidApp.php
?siteid={id}
Returns ~400KB JSON with images[], reviews[] (under comments), address, elevation, phone, email, open_dates, notes, author, etc. Same trim-the-preamble + same --proxies requirement.
Pagination / wider radius: there is no offset parameter — offset=20 is silently ignored. The only knob is max_sites=40 (default 20). To cover a wider geographic area than the implicit ~25-mile radius, geocode multiple anchor points and union the result sets, de-duplicating by id.
When --proxies is exhausted, the proxy IP is rate-limited (returns the 21-byte whitespace-only preamble repeatedly — see gotcha), or you need a query whose server-side geocoding succeeds in-browser but fails via the API directly:
browse cloud sessions create --keep-alive (no --proxies, no --verified — site has no anti-bot wall and HTTP/2 errors out when proxies are on — ERR_HTTP2_PROTOCOL_ERROR on every navigation).browse open https://freecampsites.net/ --remote --session "$sid".browse snapshot and locate the search ref via accessibility tree: searchbox: Enter a Location (placeholder is the canonical anchor — ref index varies per snapshot; do not hardcode).browse click {ref} then browse fill {ref} "Sedona, Arizona" — wait 2s for the Photon-backed autocomplete dropdown to render.browse click {autocomplete-suggestion-ref} — first item in the dropdown is a listitem containing a <strong> with the place name. The click navigates the URL hash to #!Sedona,+Arizona,+United+States and triggers the same androidApp.php XHR shown above.Array.from(document.querySelectorAll('a[href*=sitedetails]')).map(a => ({
url: a.href, // "https://freecampsites.net/#!{id}&query=sitedetails"
id: a.href.match(/#!(\d+)/)[1],
// The parent .table_row carries onmouseenter="mouseInRow({lat}, {lon}, ...)"
}))
Or, simpler: fire the same XHR yourself from browse eval:
new Promise(r => {
const x = new XMLHttpRequest();
x.open('GET', '/wp-content/themes/freecampsites/androidApp.php?location=Sedona%2C+Arizona&coordinates=Sedona%2C+Arizona&advancedSearch=%7B%7D&max_sites=40');
x.onload = () => r(JSON.parse(x.responseText.trim()));
x.send();
});
The browser-originated XHR is the most reliable transport — same-origin, no proxy needed, no rate-limit symptoms seen during testing. responseText always carries the ~23-byte WordPress preamble before the JSON; trim() (or responseText.replace(/^\s+/, '')) before JSON.parse.--proxies is mandatory from data-center IPs: browse cloud fetch without --proxies returns 500 Internal Server Error for both androidApp.php and suggest.php (the site blocks well-known data-center ranges at the nginx layer). Inside a browse open session this isn't an issue because the request originates from a browser pod with a different IP class.browse cloud fetch content-field is unreliable; use --output: when fetching androidApp.php, the inline content returned by the Browserbase Fetch API often shows just "\n\n\n\n…" (the WordPress preamble) even though the full body did arrive. Always pass --output <file> and read from disk; the sizeBytes field in the fetch response is honest.--proxies --verified: opening pages on freecampsites.net inside a session created with both --proxies and --verified produces ERR_HTTP2_PROTOCOL_ERROR on every navigation. The site has no detectable anti-bot wall — drop --verified (and --proxies for the session, since the browser-originated XHRs work over the pod IP). Reserve --proxies for the direct browse cloud fetch path only.location=Bend, Oregon or location=Yellowstone, WY returns HTTP 200 with an empty body (just the WordPress whitespace preamble — 21 bytes total). The same queries with the 3-part form "Bend, Oregon, United States" or the bare 1-part form "Bend" succeed. Don't trust the "City, ST" shape — always geocode via suggest.php first and feed the result back as location=<lat>,<lon> (which is unambiguous).suggest.php is itself imperfect for state-abbreviation queries: q=Yellowstone, WY resolves top-hit to "Yellowstone County, Montana" (because "WY" is parsed as a separate token rather than a state-disambiguator). q=Yellowstone (bare) correctly resolves to "Yellowstone National Park, WY". When the user-supplied query contains , {2-letter abbr}, consider stripping the abbreviation or trying both forms and picking the higher-access_counter result./^\n{21}$/) is the silent-fail signature for "location not resolvable". The endpoint never returns an empty resultList for a successfully-geocoded location — it always returns the 20 nearest campsites regardless of distance (Antarctica → Argentina/Chile results; Pacific Ocean → Baja California results). If you need true "no campsites within N miles", filter the result list by distance on the client side after the call.advancedSearch JSON keys are activity/amenity bitmasks, not arbitrary filters: {"fee":"Free"} and {"maxDistance":100} are silently dropped and produce count=0 (the empty-pair filter {} returns the normal 20). The recognized keys are activities, amenities, featureTypes, fee (as a bitmask), etc., and the values are integer bitmasks built up by OR'ing the UI's toggle constants — the client builds them via advancedSearch[t]=advancedSearch[t]^a where a is a power of two. The full bitmask vocabulary isn't documented; client-side filtering on the resultList is more reliable than trying to push filters server-side.max_sites caps at 40: max_sites=100 still returns 40. The default (no max_sites param) returns 20. There is no offset / next-page parameter — offset=20 is silently ignored. To cover wider regions, query multiple anchor points and de-dupe by id.act-1=OHV, act-2=Biking, act-16=Hiking, act-32=Horse Trails, act-1024=Wildlife Viewing (verified). The integer in type_specific.activities is the OR of all bits — "1075" = 1+2+16+32+1024 = OHV + Biking + Hiking + Horse Trails + Wildlife Viewing. Higher bits exist; back-derive from the table_row HTML's <img class="act-amen-icon act-{N}" alt="{Name}"> images, which carry both the bit value and a human-readable label.type_specific.icon URL ends in fc_icon-tent-green-24x24.png (Free), -blue-24x24.png (Pass/Permit Required), or -red-24x24.png (Pay). The type_specific.fee string field is the canonical source — three observed values: "Free", "Pay", "Pass/Permit Required". There is no type_specific.fee === "Unknown" — every campsite is classified.suggest.php resolved to "Yellowstone County, MT", every distance is measured from the Montana centroid. Always echo back the resolved city/region from the androidApp.php response so the caller can see what was actually searched.siteid detail responses are ~400KB each and include base64-style image arrays, every review comment, and notes. Don't pre-fetch details for all 40 result rows by default — fetch on demand. The images[][0][0] field stores image URLs as \\'<url>\\' (escaped-and-quoted strings, not bare URLs); strip the leading \' and trailing \' before use.androidApp.php and suggest.php response is prefixed with ~21–23 bytes of literal \n characters (the WordPress theme's pre-output). Always .trim() (or .replace(/^\s+/, '')) before JSON.parse. The 21-byte all-newlines body is the silent-fail signature.browse cloud fetch --proxies: the same query that worked moments earlier may return 21 bytes for several consecutive attempts before returning data again. Pattern observed: cold proxy IPs sometimes get the empty response on first hit, warm IPs cache and return. Retry up to 3 times with 0.5s spacing; if still empty after that, fall back to the in-browser XHR path (a browse open session firing the same XHR via eval is the most reliable transport observed).--verified stealth needed: site has no Cloudflare / Akamai / captcha / login wall. A bare browse cloud sessions create --keep-alive is sufficient for the browser fallback.{
"success": true,
"query": "Sedona, Arizona",
"resolved": {
"name": "Sedona",
"state": "Arizona",
"country": "United States",
"latitude": 34.86974,
"longitude": -111.76099
},
"search_center": {"latitude": 34.8648, "longitude": -111.81},
"result_count": 40,
"results": [
{
"id": 4001,
"name": "FR525 & FR525C - West of Sedona",
"detail_url": "https://freecampsites.net/#!4001&query=sitedetails",
"canonical_url": "https://freecampsites.net/fr525-fr525c-west-of-sedona/",
"latitude": 34.833159,
"longitude": -111.908447,
"distance_miles": 6,
"city": "Sedona",
"county": "Yavapai County",
"region": "Arizona",
"country": "United States",
"fee": "Free",
"rating_average": 3.18,
"rating_count": 104,
"activities_bitmask": 1075,
"activities": ["OHV", "Biking", "Hiking", "Horse Trails", "Wildlife Viewing"],
"amenities_bitmask": 0,
"amenities": [],
"excerpt": "Travel 3 miles west of Lower Red Rock Loop Road on Hwy. 89A to Forest Road 525. The first 5 miles of FR 525 and all of FR 525C are …",
"icon_url": "https://freecampsites.net/wp-content/themes/freecampsites/images/map-icons/fc_icon-tent-green-24x24.png"
}
]
}
{
"success": false,
"query": "zzzzzzzzzzzz",
"error_reasoning": "Location could not be geocoded — suggest.php returned an empty FeatureCollection. Try a more specific query (full city + state + country, or lat,lon directly).",
"resolved": null,
"results": []
}
The endpoint always returns 20+ campsites for any successfully geocoded point, even if that point is mid-ocean or in Antarctica. When the resolved region doesn't match the user's intent (e.g. user asked for "Yellowstone, WY" and resolved.state came back as "Montana"), surface a warning:
{
"success": true,
"query": "Yellowstone, WY",
"resolved": {
"name": "Yellowstone County",
"state": "Montana",
"country": "United States",
"latitude": 45.93725,
"longitude": -108.27435
},
"warning": "Geocoder resolved 'Yellowstone, WY' to Yellowstone County, MT — likely not the intended location. Re-query with 'Yellowstone National Park' or coordinates (44.4280, -110.5885).",
"search_center": {"latitude": 45.93725, "longitude": -108.27435},
"result_count": 40,
"results": [/* ... 40 entries centered on Billings, MT */]
}
?siteid=){
"id": 4001,
"name": "FR525 & FR525C - West of Sedona",
"type": "campsite",
"create_date": "2011-05-01T17:21:57+00:00",
"update_date": "2026-04-27T02:29:48+00:00",
"latitude": 34.833159,
"longitude": -111.908447,
"elevation": "...",
"address": "...",
"city": "Sedona",
"county": "Yavapai County",
"region": "Arizona",
"country": "United States",
"open_dates": "Year Round",
"fee": "Free",
"phone": "...",
"email": "...",
"rating_average": 3.18,
"rating_count": 104,
"excerpt": "...",
"notes": "<HTML body of the full notes/description>",
"images": [/* array of image URL + thumbnail tuples */],
"comments": [/* array of user reviews */],
"author": "...",
"author_url": "..."
}
Prerequisites
Time Estimate
15-45 minutes depending on use case complexity
Steps
Common Pitfalls
✓ Do
✗ Don't
💡 Pro Tips
✓ Use when
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid when
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
booking.com/search-hotels-asq6cc
google.com/search-flights-ts4g1f
americanairlines.com/search-tickets-yj09ak
cruisecritic.com/extract-reviews-8r7ocj
kayak.com/compare-flights-9xc047
sixt.com/search-car-rental-oulz1k
Keeps context tight: browse-campsites is the kind of skill you can hand to a new teammate without a long onboarding doc.
We added browse-campsites from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
browse-campsites fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
Registry listing for browse-campsites matched our evaluation — installs cleanly and behaves as described in the markdown.
browse-campsites has been reliable in day-to-day use. Documentation quality is above average for community skills.
browse-campsites reduced setup friction for our internal harness; good balance of opinion and flexibility.
Useful defaults in browse-campsites — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
Solid pick for teams standardizing on skills: browse-campsites is focused, and the summary matches what you get after install.
We added browse-campsites from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
Useful defaults in browse-campsites — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
showing 1-10 of 65