API keys

Create, scope, restrict and revoke the API keys that let scripts and AI assistants act on your trail systems.

API keys let scripts, scheduled jobs and AI assistants use the Management API v1 on your behalf. A key acts as you: it can only reach trail systems you manage, with the role you have on each.

Treat keys like passwords.

#Creating a key

  1. Sign in at trailhub.org.
  2. Open User Settings → API Keys.
  3. Enter a Name that tells you what the key is for (for example "Claude assistant" or "Grooming script"). Names are up to 100 characters.
  4. Choose Access:
    • Read & write (default) — can read and change trails, points, updates and trail system details.
    • Read only — can call GET endpoints only.
  5. Optionally Limit to one trail system. By default a key can access every trail system you manage; pick one system to confine it.
  6. Choose when the key Expires: Never (default), 30 days, 90 days or 1 year.
  7. Click Create API key.

The new key appears in a green box at the top of the page, starting with th_. Copy it now. For security it is never shown again; TrailHUB stores only a SHA-256 hash. Click I have saved my key once you have put it somewhere safe.

If you need more options than the form offers (several trail systems on one key, a custom expiry in days), create the key through the API instead; see Creating keys through the API.

#Scopes

ScopeAllows
readAll GET endpoints
writeEverything read allows, plus POST, PATCH and DELETE on trails, points, updates and trail systems

A key always has at least read. The web app creates keys with either ['read'] or ['read', 'write'].

Scopes never grant more than your account can do. For example PATCH /trail-systems/:id requires the Administrator role regardless of scope, and no key can create other keys.

#Restricting a key to a trail system

A key limited to a trail system returns 403 key_restricted for any other system, and GET /trail-systems lists only the permitted system. Use this for keys you hand to a contractor or a shared device, or for an AI assistant that should only touch one system.

You can only restrict a key to systems you manage; the API rejects other IDs.

#Expiry

An expired key is refused with 401 expired_api_key. There is no renewal; create a new key and update whatever used the old one. Keys that never expire stay valid until revoked.

#The Active keys table

Under the form, Active keys lists every unrevoked key with:

  • Name, plus the trail system it is limited to and its expiry date, if any
  • Key — the first 11 characters (th_ + 8) followed by …, enough to tell keys apart
  • Access — Read & write or Read only
  • Last used — the date the key last authenticated a request (updated at most once every 5 minutes), or Never

#Revoking a key

  1. In User Settings → API Keys, find the key under Active keys.
  2. Click Revoke.
  3. Confirm.

Revocation is immediate and permanent. The next request made with that key gets 401 revoked_api_key, and anything still configured with it (an MCP server, a cron job) stops working. Revoked keys disappear from the Active keys list.

Revoke a key right away if you suspect it has leaked, for example if it was pasted into a chat, committed to a repository or stored on a device you no longer control.

#Limits

  • At most 25 active keys per account. Creating a 26th returns 400 too_many_keys; revoke one first.
  • Expiry, when set through the API, must be between 1 and 3650 days.

#Security advice

  • Prefer read-only keys whenever the consumer only needs to read, including AI assistants that answer questions about conditions.
  • One key per use. Give each script, device or assistant its own key so you can revoke one without breaking the others, and so Last used tells you something.
  • Restrict to one trail system when the consumer only needs one.
  • Set an expiry on keys for temporary projects or seasonal staff.
  • Store keys in environment variables or a secrets manager, not in source code, shared documents or chat messages.
  • Keys cannot mint other keys: the /keys endpoints accept only a signed-in session (Firebase ID token). A leaked key can therefore do damage only within its scope and restriction, and only until you revoke it.

#Creating keys through the API

The web app uses these same endpoints. They require a Firebase ID token from a signed-in session, never an API key.

MethodPathBody / notes
GET/api/v1/keysLists your keys, newest first (including revoked ones, flagged revoked: true)
POST/api/v1/keys{ "name", "scopes"?, "trailSystemIds"?, "expiresInDays"? } — response includes key exactly once
DELETE/api/v1/keys/:idRevokes the key

Field rules for POST /keys:

FieldTypeRules
namestringRequired, 1–100 characters
scopesarrayAny of read, write; defaults to ["read","write"]
trailSystemIdsarray of stringsOptional; every ID must be a system you manage
expiresInDaysnumberOptional; 1–3650

From a browser session in the TrailHUB app:

const token = await firebase.auth().currentUser.getIdToken()
const res = await fetch('https://trailhub.org/api/v1/keys', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Grooming script',
    scopes: ['read', 'write'],
    trailSystemIds: ['TS_ID_1', 'TS_ID_2'],
    expiresInDays: 180
  })
})
const { key } = await res.json() // save it; it is not shown again

The response for a created key:

{
  "id": "k8sV…",
  "name": "Grooming script",
  "prefix": "th_AbCdEfGh",
  "scopes": ["read", "write"],
  "trailSystemIds": ["TS_ID_1", "TS_ID_2"],
  "createdOn": "2026-10-01T14:03:11.000Z",
  "lastUsedOn": null,
  "expiresAt": "2027-03-30T14:03:11.000Z",
  "revoked": false,
  "key": "th_AbCdEfGh…"
}

Calling /keys with an API key instead of an ID token returns 403 id_token_required.

#Using a key

Send it in the Authorization header:

Authorization: Bearer th_…

Clients that cannot set Authorization may send X-API-Key: th_… instead. Check which account and scopes a key resolves to with:

curl -H "Authorization: Bearer th_..." https://trailhub.org/api/v1/me

Full endpoint documentation is in Management API v1.