Introducing the TrailHUB Management API and API keys
Until now, changing anything in TrailHUB meant signing in to the web app. That's still the right tool for most of the day, but it is a poor fit for a grooming machine's tablet, a script that mirrors status from another system, or an AI assistant that you'd like to just tell what happened. Starting today, there is a second door.
#What shipped
Three things, which fit together:
- The Management API (v1) at
https://trailhub.org/api/v1. Authenticated, JSON in and out, covering trail systems, trails, points of interest, and updates (notices, surface conditions and snow reports). - API keys, created in the web app under User Settings → API Keys. Each key has a scope (read only, or read & write), an optional restriction to a single trail system, and an optional expiry of 30 days, 90 days or a year.
- An MCP server that wraps the API so Claude Desktop, Claude Code, Cursor or any MCP client can manage your trails in natural language. There's a separate walkthrough for that.
#Who it's for
Trail managers automating status. If your groomer logs every pass somewhere already, or your ski patrol keeps a spreadsheet of what's open, a few lines of script can keep the public map in step with it instead of asking someone to retype it.
Integrators. Resort websites, regional tourism portals, and condition apps can now write to TrailHUB, not just read from it. A kiosk or a staff-only form can post a snow report straight into the system of record.
Agencies and organizations managing many systems. A key sees every trail system its owner manages unless you restrict it, so one credential can drive a county's worth of parks, and your permissions on each system carry over exactly as in the web app. Administrators can edit system details; trustees and other roles can read and write trails, points and updates.
And, of course, people who'd rather talk to an assistant than fill in forms.
#One set of documents
The guiding principle is simple: the API writes the same documents as the web app. There is no shadow database or import queue. When you PATCH a trail's status, that is the trail. Every write queues TrailHUB's compile step, so the public map, trail list, stats, embeds and subscriber notifications all update within a few seconds, exactly as if a manager had clicked the button.
That also means the API enforces the same rules. Setting a trail to Closed closes all of its activities. Surface conditions must say which trails they apply to. notify: true on an update sends email and SMS to subscribers, and the default is false, because that is the one action you really don't want a script doing by accident.
#A few examples
Authenticate with Authorization: Bearer th_... (or X-API-Key: th_...). Trail system and trail ids come from GET /api/v1/trail-systems and GET /api/v1/trail-systems/:id/trails.
Close every trail in a system, in one request:
curl -X POST https://trailhub.org/api/v1/trail-systems/TS_ID/trails/status \
-H "Authorization: Bearer th_..." -H "Content-Type: application/json" \
-d '{"status":"Closed","all":true}'Mark two trails groomed and track-set (shows on each trail for 24 hours unless you set expiresInHours):
curl -X POST https://trailhub.org/api/v1/trail-systems/TS_ID/updates \
-H "Authorization: Bearer th_..." -H "Content-Type: application/json" \
-d '{"type":"surfaceConditions","conditions":["groomed","track-set"],"trailIds":["TRAIL_A","TRAIL_B"]}'Post a snow report. Amounts are in the trail system's depth unit; leave out anything you don't know:
curl -X POST https://trailhub.org/api/v1/trail-systems/TS_ID/updates \
-H "Authorization: Bearer th_..." -H "Content-Type: application/json" \
-d '{"type":"snowReport","twentyFourHours":4,"fourtyEightHours":6,"baseDepth":14}'GET /api/v1 without authentication lists every endpoint and enum value, and GET /api/v1/update-types describes the fields of each update type, so a client can discover the shape of things without reading the docs.
#Security model
- Keys are hashed. The plaintext key is returned once, when it's created. TrailHUB stores a SHA-256 hash and a short prefix so you can recognize the key in your list. If you lose it, create a new one.
- Scopes and restrictions. A read-only key can call
GETendpoints only. A key restricted to a trail system cannot see or touch any other. - Key management needs a signed-in session. The
/keysendpoints (list, create, revoke) accept a Firebase ID token only, never an API key. A leaked key cannot be used to mint more keys or hide its tracks. - Revoke any time. From the API Keys tab, click Revoke; anything using that key fails immediately. The tab also shows when each key was last used.
- Permissions mirror the web app. A key can never do more than the manager who created it.
#The public read API is unchanged
The older unauthenticated read endpoints that embeds and third-party apps already use (/api/ts/:id, /api/trails/:id, /api/trail-systems, and friends) are exactly as they were. Nothing you've built against them needs to change. The Management API lives under its own /api/v1 prefix.
#What's not there yet
We'd rather tell you now than have you find out:
- No webhooks. You can't yet subscribe to "a trail changed" events; poll the read endpoints or the public API for now.
- Adding activities to a trail still happens in the web app. The API can change the status and difficulty of activities already on a trail, and create a trail from GeoJSON geometry, but can't attach a new activity.
- No image upload. Photos for trails, points and notices stay a web-app feature for now.
- Trustee management and rate limiting are also still to come.
#Where to go next
- API reference: /docs/developers/management-api/
- MCP server setup: /docs/developers/mcp-server/
- Walkthrough: Manage your trail system by talking to Claude
If you build something with it, or hit something that doesn't work the way you expected, tell us. The API is new and the fastest way for it to get better is to hear what you're trying to do.