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.
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
Time estimate: 15-45 minutes depending on use case complexity
Prerequisites
Submit your Claude Code skill and start earning
browse install freecampsites.net/browse-campsites-qyj28mCompatible with
Supported agents
| 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": "..."
}
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:
browse install freecampsites.net/browse-campsites-qyj28mFetches 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:
.cursor/skills/browse-campsites
Restart Cursor to activate browse-campsites. Access via /browse-campsitesin your agent's command palette.
Learn more:View source on GitHubSkills CLI docsAbout CursorWhat are agent skills?
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.
search-hotels
booking.com/search-hotels-asq6cc · travel · tag: travel
search-cheapest-flight
skyscanner.net/search-cheapest-flight-v8nvut · travel · tag: travel
search-flights
google.com/search-flights-ts4g1f · travel · tag: travel
search-listings
airbnb.com/search-listings-ddgioa · travel · tag: travel
create-vegan-itinerary
spinach.guide/create-vegan-itinerary-6ztwyn · travel · tag: travel
search-tickets
americanairlines.com/search-tickets-yj09ak · travel · tag: travel
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