Public read API
The unauthenticated legacy endpoints for reading trail systems, trails and points, and the format of the published GeoJSON file.
The public read API is the original TrailHUB API. It is read-only and unauthenticated: anyone who knows a trail system's ID can fetch its data. It is what TrailHUB's own embeds use, and it remains available alongside the newer Management API v1.
Base URL: https://trailhub.org/api
A trail system's ID is the string in its TrailHUB URL, for example https://trailhub.org/ts/<ID>.
#Before you start
- CORS. Browser calls to these endpoints are only allowed from an allowlist of origins (TrailHUB's own domains and a few partner sites). A
fetch()from your website will fail with a CORS error unless your origin has been added. Server-side calls (curl, Node, PHP, Python, a serverless function) work from anywhere, because the allowlist only applies when the browser sends anOriginheader. If you need browser access from your domain, ask us to add it, or proxy the call through your own server. The Management API v1 does not have this restriction. - No plan check is enforced in code, although developer access was historically sold as a top-tier (Double Diamond / Star) feature.
- No rate limit is enforced. Please poll no more often than every few minutes and cache the GeoJSON file.
- Dates in Firestore documents come back as
{ "_seconds", "_nanoseconds" }objects and coordinates as{ "_latitude", "_longitude" }, not as ISO strings. (The Management API v1 normalizes these.) - Errors return an empty body with HTTP status
500, including for IDs that do not exist.
#Endpoints
#GET /api/trail-systems
All active trail systems as a GeoJSON FeatureCollection of Point features, one per system, centered on each system's geolocation. The file is rebuilt every 3 hours. Inactive and blocked systems are omitted.
Feature properties:
| Property | Type | Description |
|---|---|---|
name | string | Trail system name |
tsId | string | Trail system ID, for use with the other endpoints |
openTrails | number | Trails currently Open |
cautionTrails | number | Trails currently Caution |
closedTrails | number | Trails currently Closed |
activities | array | Activity summaries (see Activity objects) |
hiddenActivities | array | Activities the manager has hidden from public lists (may be absent) |
curl https://trailhub.org/api/trail-systems#GET /api/ts/:id
One trail system document. Internal fields (users, permissions, ownerId, createdBy, updatedBy, geoJsonPath) are removed, and geoJsonUrl is added.
| Field | Type | Description |
|---|---|---|
name | string | Name |
description | string | Public description |
phoneNumber, website | string | Public contact details |
language | string | en or fr |
inactive | boolean | Hidden from the public map when true |
geolocation | object | { _latitude, _longitude } |
depthMeasurement | string | inches or centimeters |
lengthMeasurement | string | miles or kilometers |
tempMeasurement | string | fahrenheit or celsius |
totalTrails, openTrails, cautionTrails, closedTrails | number | Trail counts, excluding lifts and hidden trails |
totalDistance, openDistance | number | Kilometers; openDistance includes Caution trails |
activities | array | Activity summaries (see below) |
updates | object | Active system-level updates keyed by type: notice, snowReport (see Updates) |
weather | object | Hourly weather snapshot (paid tiers only; may be absent) |
geoJsonUrl | string | Public URL of the compiled GeoJSON file (see Published GeoJSON) |
createdOn, updatedOn | object | Firestore timestamps |
relatedTrailSystems | array | { tsId, name } objects, if configured |
iconPath | string or false | Icon URL, if uploaded |
curl https://trailhub.org/api/ts/TS_IDThe geoJsonUrl changes on every recompile (the file name includes a date and a random suffix), so fetch /api/ts/:id first rather than caching the GeoJSON URL for long.
#Activity objects
Each entry of activities (on a trail system) looks like:
| Property | Description |
|---|---|
value | Machine key, e.g. xcSkiing, hiking, fatBiking |
label | Display label |
iconClass | CSS icon class used by the web app |
totalTrails, openTrails, cautionTrails, closedTrails | Trail counts for this activity |
totalDistance, openDistance, cautionDistance, closedDistance | Kilometers |
On a trail, each activity carries value, label, iconClass, status (Open, Caution, Closed) and optionally difficulty.
#GET /api/trails/:id
All trails for trail system :id, as a JSON array ordered by order ascending. createdBy and updatedBy are removed; id is added.
| Field | Type | Description |
|---|---|---|
id | string | Trail ID |
trailSystemId | string | Owning system |
name, description | string | |
status | string | Open, Caution, Closed, or None (derive from activities) |
difficulty | string | circle, square, diamond, doubleDiamond, terrainPark, notRated |
type | string | Standard or Lift |
lineStyle | string | Solid or Dashed |
distance | number | Kilometers |
hidden, oneWay, oneWayReversed | boolean | |
order | number | Sort order; lower first |
categories | array of strings | Manager-defined groupings |
activities | array | Per-activity status (see above) |
geoJson | string | Stringified GeoJSON FeatureCollection of the trail's line(s); parse it with JSON.parse |
geolocation | object | First coordinate of the line |
elevations | array or false | 50 evenly spaced elevations in meters, or false if not yet computed |
createdOn, updatedOn | object | Firestore timestamps |
curl https://trailhub.org/api/trails/TS_ID#GET /api/trail/:id
One trail document by trail ID, same fields as above (without id).
#GET /api/point/:id
One point of interest by point ID. Fields: name, description, markerClass, status, hidden, waitTime, webCamUrl, diningOptions, geolocation, trailSystemId, createdOn, updatedOn. See the Management API for the markerClass values.
There is no public "list points for a system" endpoint; points are included in the published GeoJSON instead.
#GET /api/weather/:id
Intended to return a live forecast for the trail system's location as a one-element array. This endpoint currently depends on OpenWeather's One Call 2.5 API, which OpenWeather has retired, so it is likely to return 500. Use the weather field on /api/ts/:id (hourly snapshot, paid tiers) or your own weather provider instead.
#Published GeoJSON
The file at geoJsonUrl is a GeoJSON FeatureCollection containing every trail line and every point of interest for the system, with enough properties to draw a complete status map. It is rebuilt within seconds of any change made in the web app, through the Management API or by the Grooming Tracker. The URL is public and needs no credentials, and it is not subject to the CORS allowlist, so you can load it directly in a browser map library such as MapLibre, Leaflet or Mapbox GL.
#Trail features
Geometry: LineString (a trail stored as a MultiLineString or multi-feature collection appears as several features with the same trailId).
| Property | Type | Description |
|---|---|---|
trailId | string | Trail ID |
name | string | |
description | string | |
status | string | Open, Caution or Closed. A trail whose stored status is None is resolved here: Open if any activity is Open, else Caution if any is Caution, else Closed |
difficulty | string | circle, square, diamond, doubleDiamond, terrainPark, notRated or "" |
type | string | Standard or Lift |
lineStyle | string | Solid or Dashed |
distance | number | Kilometers |
hidden | boolean | Manager has hidden the trail; filter these out for public maps |
oneWay, oneWayReversed | boolean | Direction arrows |
order | number | Sort order |
categories | array of strings | |
activities | array | Activity objects ordered Open, Caution, Closed, other |
showInLists | boolean | true only on the first feature of a trail, so a multi-segment trail is listed once |
updates | object | Active trail-level updates keyed by type, e.g. { "surfaceConditions": [ … ] } (see below) |
#Point features
Geometry: Point ([lng, lat]).
| Property | Type | Description |
|---|---|---|
pointId | string | Point ID |
name | string | |
markerClass | string | Icon type, e.g. parking, trailhead, lodge, hazard, web-cam |
status | string | Open, Caution or Closed |
hidden | boolean | |
waitTime | number | Minutes (for wait-time points) |
diningOptions | boolean or object | Dining details, when configured |
Point descriptions and webcam URLs are not in the file; fetch /api/point/:id for those.
#Updates
Updates are the notices, surface-condition reports and snow reports managers post. Only unexpired updates are published.
- System-level updates (
notice,snowReport) appear on the trail system document underupdates.<type>[], newest first. For example the current snow report isupdates.snowReport[0]. - Trail-level updates (
surfaceConditions, and notices attached to specific trails) appear on each affected trail feature underproperties.updates.<type>[].
Useful fields on an update: description, severity (info, warning, danger) and linkUrl for notices; conditions (array of { value, text, en, fr, iconClass }) for surface conditions; the snow fields (baseDepth, twentyFourHours, fourtyEightHours, sevenDays, seasonTotal, lastSnowAmount, lastSnowDate, lastSnowTime, upperElevationDepth, lastSnowMakingDate) for snow reports; plus expirationDate and updatedOn. See the Management API for the full value lists.
#Example: a minimal status map
Server side, or from an allowlisted origin, fetch the system to get the GeoJSON URL; the GeoJSON itself can be fetched from any origin.
const ts = await fetch('https://trailhub.org/api/ts/TS_ID').then(r => r.json())
const geo = await fetch(ts.geoJsonUrl).then(r => r.json())
const colors = { Open: '#2e7d32', Caution: '#f9a825', Closed: '#c62828' }
const trails = geo.features.filter(f =>
f.geometry.type === 'LineString' && !f.properties.hidden && f.properties.type !== 'Lift')
for (const f of trails) {
console.log(f.properties.name, f.properties.status, colors[f.properties.status])
}If you need to change data rather than read it, use the Management API v1.