---
title: "Get Site Reviews"
method: GET
path: "/api/v1/parks/sites/{site_id}/reviews"
tags: ["parks"]
---

# Get Site Reviews

`GET /api/v1/parks/sites/{site_id}/reviews`

## 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/sites/{site_id}/reviews`
- Auth: none declared

One site's guest reviews, verbatim, newest first by default.

For PERMITS this is a surface Recreation.gov does not have: their page
lists a permit's reviews as one undifferentiated stream and publishes no
division id, so a user comparing two entry points has a score and nothing
to read behind it. Every review still names the place it was written
about, so the split exists in their data and is simply never shown.

For CAMPGROUNDS they already show reviews per campsite — which is why the
harvester gets an exact ``campsite_id`` join there. This route is not
uncovering anything on that surface, only putting it in the card.

Offset paging rather than a keyset cursor: the deterministic ORDER BY makes
it stable, the page is capped, and a review list is short enough that the
offset never walks far. A keyset would need the sort key echoed back and
gains nothing at this size.

Unrated sites return an empty list, not a 404 — a site with no reviews is
an ordinary answer, and the card asks before it knows.

## Path parameters

- `site_id` string, required

## Query parameters

- `sort` string: recent | rating_desc | rating_asc
- `cursor` integer: Rows already shown
- `limit` integer

## Response `200`

Successful Response

- SiteReviewListResponse: One chunk of a site's reviews, plus where the next chunk starts.
  - `site_id` string, required
  - `total` integer, required: Every review we hold for this site.
  - `next_cursor` integer, nullable: Pass back as ``cursor`` for the next chunk; null at the end of the list, which is how the client knows to stop asking rather than inferring it from a short page.
  - `reviews` SiteReviewResponse[]
    - `id` string, required
    - `rating` integer, nullable: Stars 1-5. Null when the review carried no score — which must render as absent, never as zero.
    - `text` string, nullable: The review, capped at 1000 characters at harvest.
    - `author` string, nullable: 'Jason M.' — first name and last initial, the form the source publishes. Never a full name.
    - `created_at` string, date-time, nullable: When the guest wrote it, not when we read it.
    - `helpful_votes` integer

## 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)
