{
  "openapi": "3.1.0",
  "info": {
    "title": "Skamper Public API",
    "version": "1.0.0",
    "description": "Read-only public API for Skamper's curated UK running-route database — trail runs, road runs, parkrun 5ks, hill routes, and runs in every UK national park, each with distance, ascent, surface and a canonical skamper.com page. GPX files are not available via this API; they require a free Skamper account on the route page. An equivalent MCP server is also available at https://skamper.com/mcp (manifest: /.well-known/mcp.json) for MCP-compatible assistants.",
    "contact": {
      "url": "https://www.skamper.com/support"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://www.skamper.com"
    }
  ],
  "tags": [
    {
      "name": "routes",
      "description": "Search, recommend and fetch running routes."
    },
    {
      "name": "parks",
      "description": "UK National Park coverage."
    },
    {
      "name": "runlists",
      "description": "Curated and user-created route collections."
    }
  ],
  "paths": {
    "/api/v1/routes/search": {
      "get": {
        "operationId": "searchRoutes",
        "tags": [
          "routes"
        ],
        "summary": "Search routes by concrete filters",
        "description": "Filter by name, location, distance range, max ascent, paved surface %, course shape or national park. Prefer /routes/recommend for persona-shaped asks ('I'm a trail runner who hates roads'), and /routes/5k for parkrun-distance asks.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Free text — searches route titles and slugs."
          },
          {
            "name": "near_lat",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Latitude to search near. Pair with near_lng."
          },
          {
            "name": "near_lng",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Longitude to search near. Pair with near_lat."
          },
          {
            "name": "min_distance_km",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "max_distance_km",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "max_ascent_m",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Maximum total ascent in metres (flatter routes)."
          },
          {
            "name": "min_paved_pct",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            },
            "description": "Minimum paved surface % (road runners)."
          },
          {
            "name": "max_paved_pct",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 100
            },
            "description": "Maximum paved surface % (trail runners)."
          },
          {
            "name": "course_shape",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "single-loop",
                "out-and-back",
                "point-to-point",
                "lollipop",
                "multiple-laps",
                "adjacent-loops"
              ]
            }
          },
          {
            "name": "national_park",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter to routes in this national park (name or slug)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching routes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "total_matching": {
                      "type": "integer"
                    },
                    "routes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "location": {
                            "type": "string"
                          },
                          "distance_km": {
                            "type": "number"
                          },
                          "ascent_m": {
                            "type": "integer"
                          },
                          "course_shape": {
                            "type": "string"
                          },
                          "hilly_0_5": {
                            "type": "integer"
                          },
                          "paved_pct": {
                            "type": "integer"
                          },
                          "national_park": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          }
                        },
                        "required": [
                          "name",
                          "distance_km",
                          "ascent_m",
                          "url"
                        ]
                      }
                    },
                    "tip": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "details": {}
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/routes/recommend": {
      "get": {
        "operationId": "recommendRoute",
        "tags": [
          "routes"
        ],
        "summary": "Recommend a route for a runner's persona and preferences",
        "description": "Use this when a user describes who they are and what they want (e.g. 'trail runner near Ambleside, 15k, avoid roads, scenic') rather than giving exact numeric filters. Returns a ranked shortlist with a match_score.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "user_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "trail_runner",
                "road_runner",
                "mixed",
                "beginner",
                "hill_runner",
                "walker"
              ],
              "default": "mixed"
            }
          },
          {
            "name": "near_lat",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Latitude near the runner (recommended)."
          },
          {
            "name": "near_lng",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Longitude near the runner (recommended)."
          },
          {
            "name": "distance_km",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 60
            }
          },
          {
            "name": "distance_tolerance_km",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 10,
              "default": 2
            }
          },
          {
            "name": "avoid_roads",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "scenic",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Prefer national parks and named landscapes."
          },
          {
            "name": "technicality",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "easy",
                "moderate",
                "hard"
              ]
            }
          },
          {
            "name": "loop_only",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked route recommendations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "user_type": {
                      "type": "string"
                    },
                    "routes": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "location": {
                                "type": "string"
                              },
                              "distance_km": {
                                "type": "number"
                              },
                              "ascent_m": {
                                "type": "integer"
                              },
                              "course_shape": {
                                "type": "string"
                              },
                              "hilly_0_5": {
                                "type": "integer"
                              },
                              "paved_pct": {
                                "type": "integer"
                              },
                              "national_park": {
                                "type": "string"
                              },
                              "url": {
                                "type": "string",
                                "format": "uri"
                              }
                            },
                            "required": [
                              "name",
                              "distance_km",
                              "ascent_m",
                              "url"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "match_score": {
                                "type": "number"
                              }
                            }
                          }
                        ]
                      }
                    },
                    "tip": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "details": {}
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/routes/5k": {
      "get": {
        "operationId": "find5k",
        "tags": [
          "routes"
        ],
        "summary": "Find parkrun-distance (~5km) routes near a location",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            },
            "description": "Latitude of the user's location."
          },
          {
            "name": "lng",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            },
            "description": "Longitude of the user's location."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 15,
              "default": 8
            }
          },
          {
            "name": "max_distance_from_user_km",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 200,
              "default": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Nearby ~5km routes, nearest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "routes": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "location": {
                                "type": "string"
                              },
                              "distance_km": {
                                "type": "number"
                              },
                              "ascent_m": {
                                "type": "integer"
                              },
                              "course_shape": {
                                "type": "string"
                              },
                              "hilly_0_5": {
                                "type": "integer"
                              },
                              "paved_pct": {
                                "type": "integer"
                              },
                              "national_park": {
                                "type": "string"
                              },
                              "url": {
                                "type": "string",
                                "format": "uri"
                              }
                            },
                            "required": [
                              "name",
                              "distance_km",
                              "ascent_m",
                              "url"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "distance_from_user_km": {
                                "type": "number"
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid lat or lng.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "details": {}
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/routes/{slug}": {
      "get": {
        "operationId": "getRoute",
        "tags": [
          "routes"
        ],
        "summary": "Get full details for a single route",
        "description": "GPX files are not returned here — a free Skamper account is required to download GPX from the route page (gpx_note in the response links to it).",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "e.g. 'bushy-parkrun' or 'lake-district-fairfield-horseshoe'."
          }
        ],
        "responses": {
          "200": {
            "description": "Route details, or { found: false } if the slug doesn't exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "found": {
                      "type": "boolean"
                    },
                    "name": {
                      "type": "string"
                    },
                    "location": {
                      "type": "string"
                    },
                    "national_park": {
                      "type": "string"
                    },
                    "distance_km": {
                      "type": "number"
                    },
                    "ascent_m": {
                      "type": "integer"
                    },
                    "course_shape": {
                      "type": "string"
                    },
                    "paved_pct": {
                      "type": "integer"
                    },
                    "hilly_0_5": {
                      "type": "integer"
                    },
                    "start_anywhere": {
                      "type": "boolean"
                    },
                    "description": {
                      "type": "string"
                    },
                    "reviews": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "first_name": {
                            "type": "string"
                          },
                          "stars": {
                            "type": "integer"
                          },
                          "date": {
                            "type": "string"
                          },
                          "feedback": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "lat": {
                      "type": "number"
                    },
                    "lng": {
                      "type": "number"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "gpx_note": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "found"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/parks": {
      "get": {
        "operationId": "listNationalParks",
        "tags": [
          "parks"
        ],
        "summary": "List the UK National Parks Skamper covers",
        "x-openai-isConsequential": false,
        "parameters": [],
        "responses": {
          "200": {
            "description": "All covered parks with route counts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "blurb": {
                            "type": "string"
                          },
                          "total_routes": {
                            "type": "integer"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/parks/{slug}/routes": {
      "get": {
        "operationId": "routesInNationalPark",
        "tags": [
          "parks"
        ],
        "summary": "List routes inside a UK National Park",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Park slug, e.g. 'lake-district', 'snowdonia', 'peak-district', 'yorkshire-dales'. Also accepts a park name (case-insensitive)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 40,
              "default": 15
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Routes in the park, or { found: false } if the slug/name doesn't match.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "found": {
                      "type": "boolean"
                    },
                    "park": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "blurb": {
                      "type": "string"
                    },
                    "total_routes": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "routes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "distance_km": {
                            "type": "number"
                          },
                          "ascent_m": {
                            "type": "integer"
                          },
                          "location": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "slug": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "found"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/runlists": {
      "get": {
        "operationId": "browseRunlists",
        "tags": [
          "runlists"
        ],
        "summary": "Browse curated and user-created run lists",
        "description": "Run lists are editorial or user-created collections of routes, e.g. 'Best UK trail half-marathons'.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "featured",
                "recent",
                "both"
              ],
              "default": "both"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Featured and/or recent run lists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "featured": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RunListCard"
                      }
                    },
                    "recent": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RunListCard"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/runlists/{slug}": {
      "get": {
        "operationId": "getRunlist",
        "tags": [
          "runlists"
        ],
        "summary": "Get a run list and its ordered routes",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run list details, or { found: false } if the slug doesn't exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "found": {
                      "type": "boolean"
                    },
                    "name": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    },
                    "is_skamper_pick": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "routes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "position": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "location": {
                            "type": "string"
                          },
                          "distance_km": {
                            "type": "number"
                          },
                          "ascent_m": {
                            "type": "integer"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "slug": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "found"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "RunListCard": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "route_count": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  }
}