---
title: "List Park Sites"
method: GET
path: "/api/v1/parks/{park_id}/sites"
tags: ["parks"]
---

# List Park Sites

`GET /api/v1/parks/{park_id}/sites`

## Request

- Base URL: `https://api.alert.camp` (the document declares no server; this is the origin it was published from)
- URL: `https://api.alert.camp/api/v1/parks/{park_id}/sites`
- Auth: none declared

Every site in a park, in one call.

The same rows ``/campgrounds/{id}/sites`` returns, unioned across the
park — for a caller that wants the whole park rather than one campground.
The wizard's activity step is the case that motivated it: it counts the
sites matching an activity + the refine filters, which meant one request
per campground, 78 of them on Deschutes NF, ~15s before Next could enable.

Why that collapses so well: a request costs ~0.18s fixed (warm connection)
plus ~1.2ms per site, and the fixed part is the park lookup, the platform
resolve and the hot-site batch — work that is IDENTICAL for every
campground in the park.  Doing it once turns 78 x 0.18s into 1 x 0.18s and
leaves only the per-site cost, which the data volume forces either way.

Deliberately NOT frozen by activity or container kind: callers intersect
these sites with the campground list from their own versioned campgrounds
route (``zone_campgrounds`` maps each zone to its campground), so a
campground that route hides stays hidden without this one having to know
which freeze applies to which caller.

## Path parameters

- `park_id` string, required

## Query parameters

- `active_only` boolean: Only return active sites (and active zones/campgrounds)

## Response `200`

Successful Response

- ParkSiteListResponse: Every site in a park, in ONE response. The per-campground route answers the same data in one call per campground, and the fixed cost of a call (~0.18s warm: routing, the campground / park / zone lookups, the hot-site batch, response construction) is ~150x the cost of a single site (~1.2ms). So a 78-campground park like Deschutes NF spent ~14s of its ~15.6s paying that fixed cost 78 times over. This route pays it once, which is the whole point of it. ``zone_campgrounds`` is what makes the collapse lossless: the caller still needs to know which campground a site belongs to (the wizard filters per-campground, and the activity rules differ by campground), and ``SiteResponse`` carries only ``zone_id``. Sending the mapping once beats widening every site row with a campground id that repeats. Not a frozen surface: no shipped build calls it, and callers intersect these sites with the campground list they already fetched from their own (frozen) campgrounds route, so a campground hidden there stays hidden.
  - `sites` SiteResponse[], required
    - `id` string, required
    - `zone_id` string, required
    - `name` string, required
    - `description` string, nullable, required
    - `url` string, nullable, required
    - `accommodation_type` string, nullable, required
    - `availability_period` string, nullable, required
    - `features` object, nullable, required
    - `pets_allowed` boolean, nullable, required
    - `has_water` boolean, nullable, required
    - `has_electricity` boolean, nullable, required
    - `has_sewer` boolean, nullable, required
    - `is_accessible` boolean, nullable
    - `is_double_site` boolean, nullable
    - `is_pull_through` boolean, nullable
    - `is_tent_only` boolean, nullable
    - `price_per_night` string, nullable, required
    - `max_occupancy` integer, nullable
    - `display_order` integer, nullable
    - `max_tent_pads` integer, nullable
    - `can_start_route` boolean
    - `routable` boolean
    - `single_permit` boolean
    - `op_min_nights` integer, nullable
    - `op_max_nights` integer, nullable
    - `op_long_stay_min_nights` integer, nullable
    - `op_long_stay_search_tab_id` integer, nullable
    - `op_long_stay_api_bcat` integer, nullable
    - `op_allowed_equipment` integer[], nullable: Allowed sub-equipment IDs (GoingToCamp). Used by the wizard to filter sites that don't fit the user's equipment.
    - `op_booking_category_id` integer, nullable: Booking category ID (2=roofed accommodation). Used for site-level filtering within mixed zones.
    - `camp_type` string, nullable: Canonical CampType bucket ('Camping', 'Chalet', 'CanoeCamping', 'Peche', etc.). Provider-agnostic; preferred over op_booking_category_id for activity-type filtering.
    - `lat` number, nullable: WGS84 latitude. Populated for SEPAQ chalet/pêche units (bookable points spread across vast wildlife reserves). NULL for photo-overlay sites — those carry map_id/map_x/map_y instead, exposed via the /campgrounds/{id}/maps endpoint. Mobile's GeographicSitePicker uses this + lng to plot pins on flutter_map.
    - `lng` number, nullable: WGS84 longitude. See lat docstring for shape.
    - `permit_district` string, nullable: The land manager's own grouping for this row — the wilderness a trailhead sits in (Central Cascades: 'Three Sisters', 'Mt. Jefferson', 'Mt. Washington'). Parsed at seed time from the upstream name, which ships it as a prefix ('Three Sisters - Broken Top Trailhead'). Lets the day-use grid section eighteen rows the way the provider itself presents them, instead of one flat eighteen-row scroll. NULL where the provider publishes no grouping — Parks Canada day use has none.
    - `sepaq_unit_id` integer, nullable: SEPAQ-specific reservable unit id from /carte/resultats. Stable across re-extractions and useful as a provider-stable join key. NULL for non-SEPAQ sites.
    - `coord_is_approximate` boolean: TRUE when lat/lng was inferred rather than published — a handful of permit divisions have no coordinate anywhere in the provider's payload, so theirs come from a gazetteer and may be the named feature rather than its trailhead. Clients that present a coordinate as precise should say otherwise for these.
    - `map_url` string, nullable: External map for this site published by the land manager, e.g. the Forest Service trail map for a permit trailhead. Distinct from the booking page in `url`: the site card offers both, and tapping the site image opens THIS when present. NULL when the land manager publishes none.
    - `permit_trailhead_code` string, nullable: The land manager's own label for a permit division ('12A'). Providers stamp the TRAILHEAD's coordinate on every division leaving it, so Yosemite's 56 divisions sit on 43 distinct lat/lng pairs and no amount of zoom separates them. The BASE of this code ('12' of '12A') is the shared pin, so a picker that groups by base reproduces the printed map's hierarchy: one row per trailhead, its competing permits nested under it. NULL for non-permit sites and for divisions the land manager does not number.
    - `permit_addon_eligibility` string[], nullable: Slugs of OTHER permits this division qualifies the holder for, e.g. ['half_dome']. A cross-permit relationship published by the land manager (Half Dome is its own permit, 234652), not an attribute of the wilderness permit itself. NULL when none.
    - `has_image` boolean
    - `is_active` boolean, required
    - `is_hot` boolean: Whether this site is in the curated hot_sites set (eligible for hot-spot notifications). Populated by the API handler from a batch lookup; defaults to False when constructed outside the handler chain.
    - `rating` number, nullable: Mean guest star rating, 1-5, or NULL when this site has no reviews. NULL and 0.0 are different answers and must not render the same way, which is why this is nullable rather than defaulted.
    - `review_count` integer: How many guest reviews resolved to this site. Shown beside the rating so a 4.6 from 200 reviews reads differently from a 5.0 from one.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
  - `total` integer, required
  - `park_id` string, required
  - `zone_campgrounds` object: zone_id -> campground_id, for every zone represented

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/alert/apis/alertcamp.md) · [All operations](https://skmtc.dev/alert/apis/alertcamp/llms.txt) · [OpenAPI document](https://skmtc.dev/alert/apis/alertcamp/revisions/864c8c9ef833?raw)
