{"id":"ec6a32cf-44a3-4210-9606-930a351716f3","entityType":"agent","slug":"clawhub-chrischall-alltrails-fpx","name":"alltrails-fpx","canonicalUrl":"https://www.xpersona.co/agent/clawhub-chrischall-alltrails-fpx","canonicalPath":"/agent/clawhub-chrischall-alltrails-fpx","generatedAt":"2026-10-10T11:53:34.924Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-10T09:27:56.493Z","emptyReason":null},"description":"Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the alltrails-mcp server — one-shot calls through a signed-in browser tab. Use when you want AllTrails data without the MCP, in a script, or on a machine where the MCP isn't installed. Skill: alltrails-fpx Owner: chrischall Summary: Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the alltrails-mcp server — one-shot calls through a signed-in browser tab. Use when you want AllTrails data without the MCP, in a script, or on a machine where","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.5K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:alltrails-fpx","sourceUrl":"https://clawhub.ai/chrischall/alltrails-fpx","homepage":"https://clawhub.ai/chrischall/skills/alltrails-fpx","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/chrischall/alltrails-fpx","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/chrischall/skills/alltrails-fpx","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":64,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell w"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T09:27:56.493Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T09:27:56.493Z","emptyReason":null},"stars":null,"forks":null,"downloads":1525,"packageName":null,"latestVersion":"3.1.8","tractionLabel":"1.5K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T09:27:56.465Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T09:27:56.493Z","lastCrawledAt":"2026-10-10T09:27:56.465Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T09:27:56.465Z","lastVerifiedAt":null,"highlights":[{"version":"3.1.8","createdAt":"2026-10-09T23:23:01.723Z","changelog":"- Removed the skill-card.md file. - No changes to core functionality or usage; documentation and setup remain the same.","fileCount":4,"zipByteSize":7473},{"version":"3.1.7","createdAt":"2026-10-07T13:39:16.319Z","changelog":"- Removed the skill-card.md file. - No changes to functionality or user-facing features.","fileCount":4,"zipByteSize":7608},{"version":"3.1.6","createdAt":"2026-10-05T02:48:03.404Z","changelog":"- Removed the file skill-card.md. - No user-facing or functional changes; documentation and functionality remain unchanged.","fileCount":4,"zipByteSize":7459},{"version":"3.1.5","createdAt":"2026-10-03T01:41:17.694Z","changelog":"- Removed the file skill-card.md. - No functional or API changes; documentation and code remain unchanged.","fileCount":4,"zipByteSize":7532},{"version":"3.1.4","createdAt":"2026-09-28T13:55:31.127Z","changelog":"- Updated documentation to reference \"ContextMint Bridge\" instead of \"Transporter\" or \"fetchproxy extension\". - Added information about ContextMint Bridge releases and installation, including GitHub link for source and SHA256-verified releases. - Removed references to now-deleted file skill-card.md.","fileCount":4,"zipByteSize":7536},{"version":"3.1.3","createdAt":"2026-09-25T15:50:40.568Z","changelog":"- Removed the file skill-card.md. - No other changes to code or documentation.","fileCount":4,"zipByteSize":7348},{"version":"3.1.2","createdAt":"2026-09-23T21:42:46.031Z","changelog":"- Removed the skill-card.md file. - No user-facing feature or behavior changes.","fileCount":4,"zipByteSize":7536},{"version":"3.1.1","createdAt":"2026-09-23T15:45:53.082Z","changelog":"- Removed the file: skill-card.md. - No changes to core functionality or documentation content.","fileCount":4,"zipByteSize":7476}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:alltrails-fpx","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T11:53:34.921Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-alltrails-fpx/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-10T09:27:56.493Z","emptyReason":null},"readme":"Skill: alltrails-fpx\n\nOwner: chrischall\n\nSummary: Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the alltrails-mcp server — one-shot calls through a signed-in browser tab. Use when you want AllTrails data without the MCP, in a script, or on a machine where the MCP isn't installed.\n\nTags: latest:3.1.8\n\nVersion history:\n\nv3.1.8 | 2026-10-09T23:23:01.723Z | auto\n\n- Removed the skill-card.md file.\n- No changes to core functionality or usage; documentation and setup remain the same.\n\nv3.1.7 | 2026-10-07T13:39:16.319Z | auto\n\n- Removed the skill-card.md file.\n- No changes to functionality or user-facing features.\n\nv3.1.6 | 2026-10-05T02:48:03.404Z | auto\n\n- Removed the file skill-card.md.\n- No user-facing or functional changes; documentation and functionality remain unchanged.\n\nv3.1.5 | 2026-10-03T01:41:17.694Z | auto\n\n- Removed the file skill-card.md.\n- No functional or API changes; documentation and code remain unchanged.\n\nv3.1.4 | 2026-09-28T13:55:31.127Z | auto\n\n- Updated documentation to reference \"ContextMint Bridge\" instead of \"Transporter\" or \"fetchproxy extension\".\n- Added information about ContextMint Bridge releases and installation, including GitHub link for source and SHA256-verified releases.\n- Removed references to now-deleted file skill-card.md.\n\nv3.1.3 | 2026-09-25T15:50:40.568Z | auto\n\n- Removed the file skill-card.md.\n- No other changes to code or documentation.\n\nv3.1.2 | 2026-09-23T21:42:46.031Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing feature or behavior changes.\n\nv3.1.1 | 2026-09-23T15:45:53.082Z | auto\n\n- Removed the file: skill-card.md.\n- No changes to core functionality or documentation content.\n\nv3.1.0 | 2026-09-20T02:51:50.929Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing functionality or documentation changes.\n\nv3.0.0 | 2026-09-17T23:36:24.934Z | auto\n\n- Removed the skill-card.md file (no longer included in the skill package).\n- No functionality or documentation changes to the skill itself.\n\nv2.3.5 | 2026-09-15T18:43:11.798Z | auto\n\n- Removed the skill-card.md file.\n- No functional changes to the skill itself.\n\nv2.3.4 | 2026-09-14T13:16:39.914Z | auto\n\n- Removed the file: skill-card.md.\n- No user-facing changes to skill functionality or documentation.\n\nv2.3.3 | 2026-09-11T02:44:34.894Z | auto\n\n- Updated SKILL.md with clarified instructions on capturing x-at-key: explicitly note capture window timing and recommend starting capture before triggering API traffic in the tab.\n- Improved troubleshooting guidance for common capture issues.\n- Refined DataDome/bot wall notes to distinguish capture timing from actual tab state.\n- Removed the skill-card.md file.\n\nv2.3.2 | 2026-09-10T17:48:48.002Z | auto\n\nVersion 2.3.2\n\n- Removed the file skill-card.md.\n- No functional or documentation changes otherwise.\n\nv2.3.1 | 2026-09-09T21:15:57.536Z | auto\n\n- Removed the skill-card.md file.\n- No changes to the skill's core functionality or documentation.\n\nv2.3.0 | 2026-09-04T22:20:26.314Z | auto\n\n- Removed sample skill card file (skill-card.md) to clean up redundant documentation.\n- No user-facing or feature changes; core functionality and instructions remain unchanged.\n\nv2.2.0 | 2026-08-29T13:53:43.052Z | auto\n\n- Removed the skill-card.md file.\n- No changes to functionality or usage.\n\nv2.1.5 | 2026-08-28T21:06:46.034Z | auto\n\n- Removed the file: skill-card.md.\n- No changes made to functionality or documentation.\n- Internal cleanup by deleting an unused or redundant file.\n\nv2.1.4 | 2026-08-28T11:34:10.175Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing functionality or behavior changes in this release.\n- Documentation and feature set remain unchanged.\n\nv2.1.3 | 2026-08-06T00:42:49.634Z | auto\n\n- Removed the file skill-card.md from the repository.\n- No user-facing or functionality changes; all usage and setup remain unchanged.\n- Documentation and usage instructions in SKILL.md are not modified.\n\nv2.1.2 | 2026-07-30T12:53:40.260Z | auto\n\n- Removed the skill-card.md file.\n- No changes to functionality or documentation content.\n\nv2.1.1 | 2026-07-27T02:56:39.517Z | auto\n\n- Introduced alltrails-fpx: fetch AllTrails data via the fpx CLI in a signed-in browser tab, no MCP server required.\n- Requires live capture and attachment of the x-at-key app header for all API calls.\n- Supports read-only endpoints: search, trail details, reviews, photos, weather, saved/completed trails, and activity feed.\n- Adds setup instructions for fpx CLI, Transporter extension, pairing, and header capture.\n- Documents error codes and troubleshooting for bridge, session, and API access.\n\nArchive index:\n\nArchive v3.1.8: 4 files, 7473 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (1766b), SKILL.md (5773b), _meta.json (132b)\n\nFile v3.1.8:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed\n([releases](https://github.com/nullnet-app/contextmint-bridge/releases) —\nChrome: load the chrome zip unpacked; use Chrome for now — Safari will ship inside the\nContextMint app, which has no public download yet), its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source and `.sha256`-checkable release zips at https://github.com/nullnet-app/contextmint-bridge.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.8:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.8\",\n  \"publishedAt\": 1791588181723\n}\n\nFile v3.1.8:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.8:skill-card.md\n\n## Description:\n\nHelps agents query AllTrails trails, reviews, photos, weather, and signed-in account activity using one-shot fpx commands through a browser tab without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and other AllTrails users use this skill to retrieve trail information and, with a signed-in browser session, their saved lists, completed trails, and activity feed from a shell without running an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Browser-mediated requests may expose personal AllTrails activity and captured request headers in shared logs or scripts.\n\nMitigation: Review account-scoped commands and avoid sharing logs or scripts containing captured headers or personal AllTrails data.\n\n## Reference(s):\n\n- [AllTrails endpoint examples](references/endpoints.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n- [ClawHub skill listing](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only requests; account-specific results require a signed-in AllTrails browser tab.]\n\n## Skill Version(s):\n\n3.1.8 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.7: 4 files, 7608 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (2070b), SKILL.md (5773b), _meta.json (132b)\n\nFile v3.1.7:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed\n([releases](https://github.com/nullnet-app/contextmint-bridge/releases) —\nChrome: load the chrome zip unpacked; use Chrome for now — Safari will ship inside the\nContextMint app, which has no public download yet), its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source and `.sha256`-checkable release zips at https://github.com/nullnet-app/contextmint-bridge.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.7:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.7\",\n  \"publishedAt\": 1791380356319\n}\n\nFile v3.1.7:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.7:skill-card.md\n\n## Description:\n\nGuides agents to query AllTrails trail and account data through a signed-in browser tab using fpx, without running an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to find trails, reviews, photos, and weather, or to retrieve authorized saved lists, completed trails, and activity from AllTrails without an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Signed-in browser access exposes private account activity, saved lists, completed trails, profile IDs, and captured headers.\n\nMitigation: Install only if comfortable granting fpx and ContextMint Bridge access to the signed-in tab; keep account data and captured headers out of logs, screenshots, public issues, and untrusted scripts.\n\nRisk: Requests to another person's account or profile could exceed the user's authorization.\n\nMitigation: Use documented endpoints only for accounts and profiles you are authorized to access.\n\n## Reference(s):\n\n- [AllTrails FPX release](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n- [AllTrails endpoint reference](artifact/references/endpoints.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance, JSON data]\n\n**Output Format:** [Markdown instructions and shell commands; AllTrails responses are JSON]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only requests; account data requires an authorized signed-in tab; route geometry is returned raw, not as GPX.]\n\n## Skill Version(s):\n\n3.1.7 (source: server-resolved release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.6: 4 files, 7459 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (1709b), SKILL.md (5773b), _meta.json (132b)\n\nFile v3.1.6:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed\n([releases](https://github.com/nullnet-app/contextmint-bridge/releases) —\nChrome: load the chrome zip unpacked; use Chrome for now — Safari will ship inside the\nContextMint app, which has no public download yet), its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source and `.sha256`-checkable release zips at https://github.com/nullnet-app/contextmint-bridge.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.6:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.6\",\n  \"publishedAt\": 1791168483404\n}\n\nFile v3.1.6:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.6:skill-card.md\n\n## Description:\n\nGuides agents in querying AllTrails trail information and a signed-in user's lists and activity using fpx through a browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and other AllTrails users can request read-only trail searches, details, reviews, photos, weather, and account activity without running an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Signed-in profile, saved-list, completed-trail, and activity-feed results may contain personal data.\n\nMitigation: Treat account results as personal data and share only what is necessary.\n\nRisk: Captured headers or command output may expose account activity.\n\nMitigation: Avoid sharing captured headers or raw command output; use the browser bridge only with an account you control.\n\n## Reference(s):\n\n- [AllTrails endpoint examples](references/endpoints.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only requests; account-specific results require a signed-in browser tab.]\n\n## Skill Version(s):\n\n3.1.6 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.5: 4 files, 7532 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (1934b), SKILL.md (5773b), _meta.json (132b)\n\nFile v3.1.5:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed\n([releases](https://github.com/nullnet-app/contextmint-bridge/releases) —\nChrome: load the chrome zip unpacked; use Chrome for now — Safari will ship inside the\nContextMint app, which has no public download yet), its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source and `.sha256`-checkable release zips at https://github.com/nullnet-app/contextmint-bridge.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.5:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.5\",\n  \"publishedAt\": 1790991677694\n}\n\nFile v3.1.5:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.5:skill-card.md\n\n## Description:\n\nHelps agents retrieve AllTrails trail details, reviews, photos, weather, and account-linked trail activity through a browser-backed shell workflow.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and other AllTrails users can use this skill to query trail information and, with a signed-in session, their saved lists, completed trails, and activity feed from a shell without the AllTrails MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The browser bridge has AllTrails site access and commands can use a signed-in session.\n\nMitigation: Install only if comfortable granting that access, and run session-backed commands only when intended.\n\nRisk: Saved lists, completed trails, activity feeds, and profile IDs can disclose personal data.\n\nMitigation: Retrieve and share account-linked results only when you intend to handle that information.\n\n## Reference(s):\n\n- [AllTrails fpx endpoint examples](references/endpoints.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration guidance, Text]\n\n**Output Format:** [Markdown with shell examples and JSON query examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only requests; account-linked results require a signed-in AllTrails session.]\n\n## Skill Version(s):\n\n3.1.5 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.4: 4 files, 7536 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (1918b), SKILL.md (5773b), _meta.json (132b)\n\nFile v3.1.4:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed\n([releases](https://github.com/nullnet-app/contextmint-bridge/releases) —\nChrome: load the chrome zip unpacked; use Chrome for now — Safari will ship inside the\nContextMint app, which has no public download yet), its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source and `.sha256`-checkable release zips at https://github.com/nullnet-app/contextmint-bridge.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.4:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.4\",\n  \"publishedAt\": 1790603731127\n}\n\nFile v3.1.4:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.4:skill-card.md\n\n## Description:\n\nGuides agents in querying AllTrails trail information and signed-in account data through a browser session using the fpx CLI without the AllTrails MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to search trails and retrieve trail details, reviews, photos, weather, and their own saved lists or activity through an AllTrails browser tab.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Third-party CLI and browser extension access an AllTrails browser session.\n\nMitigation: Confirm you trust the fpx CLI and ContextMint Bridge before installing or pairing them.\n\nRisk: Saved lists, completed trails, and activity may expose personal account data in shell output or logs.\n\nMitigation: Run account-data examples only when intended, and avoid retaining or sharing sensitive output and logs.\n\n## Reference(s):\n\n- [AllTrails via fpx on ClawHub](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n- [AllTrails endpoint examples](references/endpoints.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Guidance]\n\n**Output Format:** [Markdown with shell examples and JSON response guidance]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only requests; signed-in account data requires an active browser session.]\n\n## Skill Version(s):\n\n3.1.4 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.3: 4 files, 7348 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (1842b), SKILL.md (5372b), _meta.json (132b)\n\nFile v3.1.3:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.3:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.3\",\n  \"publishedAt\": 1790351440568\n}\n\nFile v3.1.3:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.3:skill-card.md\n\n## Description:\n\nProvides shell-based guidance for retrieving AllTrails trail and account data through a user's browser session using fpx, without running an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to retrieve trail details, reviews, photos, weather, and authorized account activity from AllTrails using shell commands and an active browser tab.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Using a signed-in browser session may expose personal account and profile data beyond the intended task.\n\nMitigation: Use account endpoints only for your own data or data you are authorized to access; revoke browser site access or pairing when no longer needed.\n\nRisk: Repeated account or profile queries could enable bulk scraping.\n\nMitigation: Avoid bulk profile scraping and keep requests limited to authorized tasks.\n\n## Reference(s):\n\n- [AllTrails fpx endpoint examples](references/endpoints.md)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration instructions, Guidance]\n\n**Output Format:** [Markdown with shell command examples and JSON response guidance]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only endpoint examples; raw route geometry rather than GPX output.]\n\n## Skill Version(s):\n\n3.1.3 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.2: 4 files, 7536 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (2345b), SKILL.md (5372b), _meta.json (132b)\n\nFile v3.1.2:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.2\",\n  \"publishedAt\": 1790199766031\n}\n\nFile v3.1.2:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.2:skill-card.md\n\n## Description:\n\nQuery alltrails.com for trail search, trail details, reviews, photos, weather, and signed-in account data from a shell through fpx and a user's own browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers, engineers, and agents use this skill to retrieve AllTrails trail data and selected signed-in account data from shell workflows without running an MCP server. It is suited for read-only lookups, scripting, and troubleshooting when a paired browser tab and fpx are available.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can use a signed-in AllTrails browser session to read personal account information such as saved lists, completed trails, and activity feed data.\n\nMitigation: Use public trail commands for ordinary lookups, and run account-specific commands only when you intentionally want that signed-in account data exposed to the agent or shell output.\n\nRisk: The fpx workflow depends on a paired Transporter browser extension and an open AllTrails tab, so command output may fail or reflect browser-session state rather than a standalone API credential.\n\nMitigation: Confirm the browser tab is open, signed in when needed, and paired before relying on command output; re-capture the AllTrails app key when requests return missing-key or stale-key errors.\n\n## Reference(s):\n\n- [AllTrails endpoints for fpx](references/endpoints.md)\n- [ClawHub release page](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration, JSON, Guidance]\n\n**Output Format:** [Markdown with shell command examples and JSON response-shape guidance]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only AllTrails calls through fpx; user/account endpoints require a signed-in browser tab and may expose account data in command output.]\n\n## Skill Version(s):\n\n3.1.2 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.1: 4 files, 7476 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (2222b), SKILL.md (5372b), _meta.json (132b)\n\nFile v3.1.1:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.1\",\n  \"publishedAt\": 1790178353082\n}\n\nFile v3.1.1:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.1:skill-card.md\n\n## Description:\n\nQuery alltrails.com for trail search, trail details, reviews, photos, weather, and a signed-in user's saved lists, completed trails, and activity feed from a shell with the fpx CLI through a signed-in browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and technical users use this skill to generate fpx shell commands for read-only AllTrails data access without running the alltrails-mcp server. It is suited for scripted trail lookup, review/photo/weather checks, and account-scoped list or activity inspection through the user's active browser session.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill accesses AllTrails through an active signed-in browser session, which can expose personal location and activity data.\n\nMitigation: Use it only with accounts and browser sessions you are comfortable granting to fpx and Transporter, and avoid logging or sharing saved lists, completed trails, profile IDs, and activity feeds unnecessarily.\n\nRisk: Persistent fpx profile or browser-extension access can outlive the immediate data retrieval task.\n\nMitigation: Remove the fpx profile or extension site access when AllTrails access is no longer needed.\n\n## Reference(s):\n\n- [AllTrails endpoints for fpx](references/endpoints.md)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration, Guidance, Markdown, JSON]\n\n**Output Format:** [Markdown guidance with shell command examples and JSON response-shape notes]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only examples rely on fpx, the Transporter browser extension, a live AllTrails tab, and captured request headers.]\n\n## Skill Version(s):\n\n3.1.1 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.1.0: 4 files, 7396 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (2015b), SKILL.md (5372b), _meta.json (132b)\n\nFile v3.1.0:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lose — the requests fire before anything is listening.\n\nStart the capture first, then feed the tab:\n\n```sh\nfpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)\n```\n\nAttach it (plus the other two protocol headers — defaults match the MCP's\n`ALLTRAILS_CALLER`/`ALLTRAILS_LOCALE`) on every `fpx get`/`post-json` call:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'\n```\n\nWithout `x-at-key` the API answers `400`; a stale/rotated key answers `400`\nor `401` — re-run the `fpx session` capture to get a fresh one and retry.\nReady-to-run request bodies for every read endpoint are in\n`references/endpoints.md`.\n\n## The one gotcha: `/me` shape\n\n`GET /api/alltrails/me` wraps the signed-in user as `{\"users\":[{\"id\":...}]}`\n— **not** `{\"user\":{...}}`. Get your own numeric user id with:\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'\n```\n\nThe per-user endpoints (lists, completed trails, activity feed) take that id\nin the path.\n\n## Exit codes (fetch verbs)\n\n- `0` — success (still check the JSON body — a non-2xx AllTrails error can\n  arrive as a 200 with an HTML/interstitial body; a non-JSON 2xx response\n  from `fpx` is almost always a DataDome challenge page, not real data).\n- `2` — bridge unavailable: extension not connected or pairing pending →\n  `fpx pair -p alltrails`, confirm an alltrails.com tab is open.\n- `3` — bot wall: the tab hasn't cleared DataDome → open a\n  `www.alltrails.com` tab, clear the challenge, then retry. (Unlike a capture\n  stall, this one really is \"fix the tab first\" — the wall is a state the tab\n  is in, not an event the call has to witness.)\n- `4` — upstream non-2xx from AllTrails (400/401 usually means a missing or\n  stale `x-at-key` — re-capture and retry).\n\n## Notes\n\n- Read-only: this only covers `GET`/read `POST` (search, reviews) endpoints —\n  AllTrails write endpoints are unverified and out of scope.\n- Money/length fields are **metric** (meters) straight from the API; convert\n  locally if you need imperial (`miles = meters / 1609.344`,\n  `feet = meters * 3.28084`).\n- `fpx health -p alltrails` shows bridge connection state when a call fails.\n- Trail/user/list ids are numeric strings as they appear in AllTrails URLs.\n- This project is developed and maintained by AI (Claude).\n\nFile v3.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.0\",\n  \"publishedAt\": 1789872710929\n}\n\nFile v3.1.0:references/endpoints.md\n\n# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \"${H[@]}\" | jq -r '.users[0].id'\n```\n\n**`{\"users\":[{\"id\":...}]}`, not `{\"user\":{...}}`.** Requires a signed-in tab.\n\n## 7. A user's saved lists\n\n```sh\nUID=... # from step 6, or another public profile's numeric id\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/lists\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 8. Items in a saved list\n\n```sh\nLISTID=... # from step 7\nfpx get \"https://www.alltrails.com/api/alltrails/lists/$LISTID/items\" -p alltrails \"${H[@]}\" \\\n  | jq '.listItems | sort_by(.order) | .[] | {trailId, order, notes, addedAt: .metadata.created}'\n```\n\nEnvelope: `{ \"listItems\": [...], \"meta\": { \"items\": N } }`. Items are\n**sparse references** — only `trailId` (hydrate with endpoint 1) plus the\ncurator's `order`/`notes`, no trail details inlined.\n\n## 9. A user's completed trails\n\n```sh\nfpx get \"https://www.alltrails.com/api/alltrails/users/$UID/trails/completed\" -p alltrails \"${H[@]}\" | jq .\n```\n\n## 10. Activity feed — directory, then items\n\n```sh\n# Directory (available feed names):\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds\" -p alltrails \"${H[@]}\" \\\n  | jq '.feeds[] | {name, displayName}'\n\n# Items — feed is local | timeline | personal:\nfpx get \"https://www.alltrails.com/api/alltrails/community/blazes/v0/users/$UID/feeds/personal?maxItems=20\" -p alltrails \"${H[@]}\" \\\n  | jq '.sections[].itemData | {itemType, timestamp, description, user: ((.user.firstName//\"\")+\" \"+(.user.lastName//\"\")), trail: .trail.name, stats: .activity.summaryStats}'\n```\n\nPaginate with `?maxItems=N&cursor=<pageInfo.nextCursor>`. Units:\n`summaryStats.distanceTotal`/`elevationGain` are **meters**, `duration` is\n**minutes** (`timeTotal`/`timeMoving`, if present, are seconds — don't\nconfuse the two).\n\n## 11. Search by name (the real search — honors query + limit)\n\nThis is the same endpoint the alltrails.com search box uses; body shape\ncaptured verbatim from the live client.\n\n```sh\ncat > /tmp/at_search.json <<'JSON'\n{\n  \"query\": \"angels landing\",\n  \"limit\": 20,\n  \"recordTypesToReturn\": [\"trail\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_search.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name, length, elevation_gain, difficulty_rating, avg_rating, area: .area_name, region: .state_name}'\n```\n\n`recordTypesToReturn` accepts any of `country`, `state`, `city`, `area`,\n`poi`, `trail`, `guide`, `filter`, `list`, `sponsored_list`. Pass the place\nkinds (`country`/`state`/`city`/`area`/`poi`) instead of `trail` to resolve a\n**location** rather than a trail:\n\n```sh\ncat > /tmp/at_location.json <<'JSON'\n{\n  \"query\": \"portland oregon\",\n  \"limit\": 10,\n  \"recordTypesToReturn\": [\"country\", \"state\", \"city\", \"area\", \"poi\"]\n}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/suggestions' @/tmp/at_location.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {name, kind: (.location_type // .type), id: .ID, objectID, slug, lat: ._geoloc.lat, lng: ._geoloc.lng, label: .location_label}'\n```\n\nNote: the resolved location `id`/`objectID` is an **Algolia search id** — it\ndoes **not** feed a `/locations/{id}/trails` listing endpoint (that endpoint\nwas retired server-side, confirmed 2026-07-08, and now 400s on every\nvariant). To find trails in/near a resolved place, feed the place **name**\nback into the trail search (endpoint above), not its id.\n\n`lat`/`lng` body params are silently **ignored** by this endpoint (verified\n2026-07-02/07-08) — results carry an implicit account/IP geo bias instead.\n\n## 12. Search — legacy no-query fallback\n\nOnly useful without a text query; every param except `limit` is ignored\n(anchored to the signed-in account's location), so this is a weak fallback —\nprefer endpoint 11 with a query.\n\n```sh\ncat > /tmp/at_search_legacy.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/explore/v1/search' @/tmp/at_search_legacy.json -p alltrails \"${H[@]}\" \\\n  | jq '.searchResults[] | {id: .ID, name}'\n```\n\n---\n\n## Response-shape reminders\n\n- Card/listing money-ish numeric fields (`length`, `elevation_gain`) are\n  **meters** everywhere — no imperial conversion server-side.\n- `objectID` is namespace-prefixed on location records (`state-38`,\n  `cityo-6641`, `area-N`) and `\"trail-{id}\"`-prefixed on trail search results\n  — prefer the numeric `ID`/`id` field when one exists.\n- A `200` response that isn't valid JSON (`jq` errors on parse) is almost\n  always a DataDome interstitial page, not real data — refresh the signed-in\n  tab and retry rather than treating the body as an error payload.\n\nFile v3.1.0:skill-card.md\n\n## Description:\n\nQuery AllTrails trail, review, photo, weather, saved-list, completed-trail, and activity-feed data from a shell through fpx using a signed-in browser tab.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to issue read-only AllTrails lookups and account-scoped queries from shell workflows when the AllTrails MCP server is unavailable or not installed.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Commands run through fpx in a signed-in browser context can access account-scoped AllTrails data such as saved lists, completed trails, profile, and activity feed.\n\nMitigation: Use only with intended AllTrails sessions, treat captured outputs as private account data, and remove the fpx profile or browser pairing when access is no longer needed.\n\nRisk: Captured app headers can become stale or fail when the browser tab has not cleared AllTrails access checks.\n\nMitigation: Recapture headers from active tab traffic and confirm the signed-in browser tab is available before retrying commands.\n\n## Reference(s):\n\n- [AllTrails fpx endpoint examples](references/endpoints.md)\n- [ClawHub release page](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration, Guidance]\n\n**Output Format:** [Markdown with inline shell commands and JSON request examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only examples; account-scoped commands may expose private AllTrails profile data.]\n\n## Skill Version(s):\n\n3.1.0 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v3.0.0: 4 files, 7513 bytes\n\nFiles: references/endpoints.md (7671b), skill-card.md (2247b), SKILL.md (5372b), _meta.json (132b)\n\nFile v3.0.0:SKILL.md\n\n---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in Transporter\n```\n\nRequirements: the **Transporter** browser extension installed, its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders","readmeExcerpt":"Skill: alltrails-fpx Owner: chrischall Summary: Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the alltrails-mcp server — one-shot calls through a signed-in browser tab. Use when you want AllTrails data without the MCP, in a script, or on a machine where","codeSnippets":[],"executableExamples":[{"language":"sh","snippet":"npm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge"},{"language":"sh","snippet":"AT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')"},{"language":"sh","snippet":"fpx session -p alltrails > /tmp/at.json &   # opens the capture window\nsleep 1 && open 'https://www.alltrails.com/explore'   # or reload the open tab\nwait && AT_KEY=$(jq -r '.capturedHeaders[\"x-at-key\"] // empty' /tmp/at.json)"},{"language":"sh","snippet":"fpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq '.trails[0] | {name, overview, length, elevation_gain}'"},{"language":"sh","snippet":"fpx get 'https://www.alltrails.com/api/alltrails/me' -p alltrails \\\n  -H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US' \\\n  | jq -r '.users[0].id'"},{"language":"sh","snippet":"AT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: alltrails-fpx\ndescription: >-\n  Query alltrails.com (trail search, trail detail, reviews, photos, weather,\n  and a signed-in user's saved lists / completed trails / activity feed) from\n  a shell with the fpx CLI (@fetchproxy/cli) instead of running the\n  alltrails-mcp server — one-shot calls through a signed-in browser tab. Use\n  when you want AllTrails data without the MCP, in a script, or on a machine\n  where the MCP isn't installed.\n---\n\n# AllTrails via fpx (no MCP)\n\nAllTrails has **no public API**; alltrails-mcp reverse-engineers the internal\none alltrails.com itself uses, fronted by **DataDome**. DataDome fingerprints\nthe HTTP client, not just the cookie — a Node/curl replay of a captured\ncookie gets **403'd** even the same day an identical in-tab fetch succeeds.\nSo every request must ride a same-origin fetch inside the user's own signed-in\ntab, which `fpx` provides (no stored-cookie mode, unlike some other fleet\nskills).\n\nEvery request also needs an **`x-at-key` app header** — a static, anonymous\nclient-identifier the web app sends on every `/api/alltrails/*` call. It is\n**not stored anywhere** — capture it live from the tab's own traffic (below).\nAn in-tab `fetch` does **not** add it automatically, so it (plus\n`x-at-caller`/`x-language-locale`) must be attached explicitly on every `fpx`\ncall, same as the MCP's `client.ts` does.\n\n## One-time setup\n\n```sh\nnpm install -g @fetchproxy/cli                  # provides `fpx`\nfpx profile add alltrails --domain alltrails.com\nfpx profile declare alltrails \\\n  --capture-header 'x-at-key@www.alltrails.com/api/alltrails/*'\nfpx pair -p alltrails                           # prints a pair code → approve in ContextMint Bridge\n```\n\nRequirements: the **ContextMint Bridge** browser extension installed\n([releases](https://github.com/nullnet-app/contextmint-bridge/releases) —\nChrome: load the chrome zip unpacked; use Chrome for now — Safari will ship inside the\nContextMint app, which has no public download yet), its Chrome\n**Site access** allowing `alltrails.com`, and an open, **signed-in**\n`www.alltrails.com` tab (the per-user tools need a real session; the trail\ntools work signed-out too). Pairing persists after the first approval.\nContextMint Bridge is the fetchproxy extension renamed, same maintainer; source and `.sha256`-checkable release zips at https://github.com/nullnet-app/contextmint-bridge.\n\n## Capture the app key, then attach it on every call\n\n`x-at-key` is captured from the *tab's own* API traffic — the capture only\nfires while the tab makes a request. `fpx session` triggers the capture and\nreturns everything declared, including `capturedHeaders`:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\n```\n\nIf `AT_KEY` comes back empty, the tab made no `/api/alltrails/*` request\n**during the capture window**. The extension listens only while the capture\ncall is in flight, so loading a page first and running `fpx session` afterwards\nis a race you usually lo"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"alltrails-fpx\",\n  \"version\": \"3.1.8\",\n  \"publishedAt\": 1791588181723\n}"},{"path":"references/endpoints.md","content":"# AllTrails endpoints for fpx\n\nEvery request needs the app-key headers captured in `SKILL.md`. All examples\nbelow assume:\n\n```sh\nAT_KEY=$(fpx session -p alltrails | jq -r '.capturedHeaders[\"x-at-key\"] // empty')\nH=(-H \"x-at-key: $AT_KEY\" -H 'x-at-caller: Mugen' -H 'x-language-locale: en-US')\n```\n\nBase URL: `https://www.alltrails.com`. Paths below are all under\n`/api/alltrails/...` unless noted. Endpoints and response shapes are\ntranscribed from `src/tools/*.ts` / `src/client.ts` / `CLAUDE.md` in\n`alltrails-mcp` (live-verified there); no shape here was guessed.\n\n---\n\n## 1. Trail detail\n\n`detail` is `basic` | `medium` (default) | `offline` (adds route geometry).\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=medium' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0] | {id: .objectID, name, overview, length, elevation_gain, difficulty_rating, avg_rating, routeType: .routeType.name, location}'\n```\n\nThe envelope is `{ \"trails\": [ {...} ] }` — a one-element array in practice.\n\n## 2. Trail reviews\n\n```sh\ncat > /tmp/at_reviews.json <<'JSON'\n{\"limit\": 20}\nJSON\nfpx post-json 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/reviews/search' @/tmp/at_reviews.json -p alltrails \"${H[@]}\" \\\n  | jq '.trail_reviews[] | {user: .user.name, rating, comment}'\n```\n\nEnvelope: `{ \"trail_reviews\": [...] }`.\n\n## 3. Trail photos\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v2/trails/10236086/photos' -p alltrails \"${H[@]}\" \\\n  | jq --arg key \"$AT_KEY\" '.photos[] | {\n      id, title, likeCount,\n      user: ((.user.firstName // \"\") + \" \" + (.user.lastName // \"\")),\n      uploadedAt: .metadata.created,\n      url: (\"https://www.alltrails.com/api/alltrails/photos/\" + (.id|tostring) + \"/image?size=large&key=\" + $key)\n    }'\n```\n\nEnvelope: `{ \"photos\": [...] }`. The record carries **no direct image URL** —\n`GET /api/alltrails/photos/{id}/image?size=large&key=<x-at-key>` 302s to the\nCDN original and is **not** DataDome-walled (fetchable directly, no bridge\nneeded, if you just want the bytes).\n\n## 4. Trail weather\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/weather-service/v2/trails/10236086/overview' -p alltrails \"${H[@]}\" | jq .\n```\n\n## 5. Trail route geometry (for GPX / mapping)\n\n```sh\nfpx get 'https://www.alltrails.com/api/alltrails/v3/trails/10236086?detail=offline' -p alltrails \"${H[@]}\" \\\n  | jq '.trails[0].defaultMap.routes[0].lineSegments[0]'\n```\n\n`pointsData` is 2-dim `(lat, lng) × 1e5`; `indexedElevationData` is 2-dim\n`(pointIndex × 100, elevationMeters × 1e5)`, one pair per point — decode both\nby dividing by `1e5` (elevation index by `100`). alltrails-mcp's\n`alltrails_get_trail_gpx` does this decode + emits GPX 1.1 in\n`src/gpx.ts`/`decodePolyline` — reproducing that Google-polyline-variant\ndecoder in shell is impractical, so this skill stops at the raw geometry;\nreach for the MCP tool (or port `decodePolyline`) if you need actual GPX XML.\n\n## 6. Your profile / user id\n\n```sh\nfpx get 'https://www.alltrails.com/ap"},{"path":"skill-card.md","content":"## Description:\n\nHelps agents query AllTrails trails, reviews, photos, weather, and signed-in account activity using one-shot fpx commands through a browser tab without an MCP server.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and other AllTrails users use this skill to retrieve trail information and, with a signed-in browser session, their saved lists, completed trails, and activity feed from a shell without running an MCP server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Browser-mediated requests may expose personal AllTrails activity and captured request headers in shared logs or scripts.\n\nMitigation: Review account-scoped commands and avoid sharing logs or scripts containing captured headers or personal AllTrails data.\n\n## Reference(s):\n\n- [AllTrails endpoint examples](references/endpoints.md)\n- [ContextMint Bridge releases](https://github.com/nullnet-app/contextmint-bridge/releases)\n- [ClawHub skill listing](https://clawhub.ai/chrischall/skills/alltrails-fpx)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Guidance]\n\n**Output Format:** [Markdown with shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only requests; account-specific results require a signed-in AllTrails browser tab.]\n\n## Skill Version(s):\n\n3.1.8 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the alltrails-mcp server — one-shot calls through a signed-in browser tab. Use when you want AllTrails data without the MCP, in a script, or on a machine where the MCP isn't installed. Skill: alltrails-fpx Owner: chrischall Summary: Query alltrails.com (trail search, trail detail, reviews, photos, weather, and a signed-in user's saved lists / completed trails / activity feed) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the alltrails-mcp server — one-shot calls through a signed-in browser tab. Use when you want AllTrails data without the MCP, in a script, or on a machine where","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1404,"uniquenessScore":45,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T09:27:56.493Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T09:27:56.493Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T11:53:34.924Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}