# TrailSplits API > HTTP API for outdoor apps: walking, running and cycling routes on signed trails, round trips from a start point, route analysis for any GPX or GeoJSON line, weather along a route at the hour you reach each part, elevation, GPX clean and simplify, place and trail search, outdoor places (huts, springs, shelters), static maps, map matching, snow, and MapLibre map styles with PMTiles and terrain. Keys are optional; registry 1.7.0. - Hosts: `https://api.trailsplits.com` for everything except weather (`https://trailsplits.com/api/weather/forecast`). Both answer browsers (CORS). One server in one region (Helsinki, Finland); no SLA. - Keys: optional. Without a key: 500 credits a day per address, 5 requests/s. With a free key from https://trailsplits.com/api/console: `Authorization: Bearer `, 30,000 credits a month, commercial use with attribution. Secret keys stay on servers; a web page uses a browser key (`ts_pub_…`) limited to its origins. - Coordinates: WGS84. Request bodies and JSON results use [lon, lat] (GeoJSON order). Older query strings use lat,lon (elevation `points`, weather `p`, snow `points`, reverse `lat`/`lon`). Lines in route answers are polyline6 (lat,lon pairs at 1e-6) unless the field is GeoJSON. - Units: metres, seconds (or named `minutes`), °C, mm, km/h, %. Unknown values are null, never 0. - Errors are JSON with a stable code: gateway `{error, status, message, retry_after?}`; /v1 calls `{error: {code, message}}`; legacy /route/v1 `{error, status_code, trailsplits: {code, message}}`. 429 carries Retry-After. A refused call costs no credits. - Attribution: show "© TrailSplits" and the data credits (© OpenStreetMap contributors; the named terrain, imagery or weather source) wherever results are shown. ODbL applies to map data: https://trailsplits.com/api/attribution ## Start - [Quickstart](https://trailsplits.com/api/quickstart): an 8 km hiking loop on a map in five minutes, one HTML file, no key. - [Full reference for agents](https://trailsplits.com/api/llms-full.txt): every operation with fields, a working request and errors, in one file. - [MCP server](https://trailsplits.com/api/agents): `https://trailsplits.com/api/mcp` (Streamable HTTP), one tool per operation, with your key. - [OpenAPI 3](https://trailsplits.com/api/reference/openapi.json) · [TypeScript client](https://trailsplits.com/api/reference/trailsplits-client.ts) · [Postman collection](https://trailsplits.com/api/reference/trailsplits-api.postman.json) - [Conventions](https://trailsplits.com/api/conventions) · [Changelog](https://trailsplits.com/api/changelog) ([Atom](https://trailsplits.com/api/changelog.xml)) · [Status](https://trailsplits.com/api/status) · [Plans and limits](https://trailsplits.com/api/pricing) · [Terms](https://trailsplits.com/api/terms) ## Operations - [Tile gateway health](https://trailsplits.com/api/reference/health): `GET https://api.trailsplits.com/healthz` - [Published artifact identities](https://trailsplits.com/api/reference/manifest): `GET https://api.trailsplits.com/pmtiles/current/manifest.json` - [Artifact status verdict](https://trailsplits.com/api/reference/status): `GET https://api.trailsplits.com/tiles/v1/status` - [Terrain pixel sampling](https://trailsplits.com/api/reference/elevation): `GET https://api.trailsplits.com/tiles/v1/elevation/{release}/sample` - [Point and batch weather forecast](https://trailsplits.com/api/reference/weather): `GET https://trailsplits.com/api/weather/forecast` - [Analyse a route you already have](https://trailsplits.com/api/reference/route-analysis): `POST https://api.trailsplits.com/v1/route-analysis` - [Round trips from a start point](https://trailsplits.com/api/reference/roundtrips): `POST https://api.trailsplits.com/v1/roundtrips` - [Weather along a route at arrival time](https://trailsplits.com/api/reference/route-weather): `POST https://api.trailsplits.com/v1/route-weather` - [Elevation profile of a line](https://trailsplits.com/api/reference/elevation-profile): `POST https://api.trailsplits.com/v1/elevation-profile` - [Route through waypoints](https://trailsplits.com/api/reference/waypoint-route): `POST https://api.trailsplits.com/v1/route` - [Pace plan for a course](https://trailsplits.com/api/reference/pace-plan): `POST https://api.trailsplits.com/v1/pace-plan` - [Clean a GPX file](https://trailsplits.com/api/reference/gpx-clean): `POST https://api.trailsplits.com/v1/gpx/clean` - [Simplify a GPX file or line](https://trailsplits.com/api/reference/gpx-simplify): `POST https://api.trailsplits.com/v1/gpx/simplify` - [Place search](https://trailsplits.com/api/reference/search): `GET https://api.trailsplits.com/search/v1/forward` - [Places near a point](https://trailsplits.com/api/reference/reverse): `GET https://api.trailsplits.com/search/v1/reverse` - [Trail search](https://trailsplits.com/api/reference/trails): `GET https://api.trailsplits.com/trails/v1/search` - [Outdoor places in an area](https://trailsplits.com/api/reference/pois): `GET https://api.trailsplits.com/pois/v1/bbox` - [Static map image](https://trailsplits.com/api/reference/static-map): `POST https://api.trailsplits.com/static-map/render` - [Map matching and trace attributes](https://trailsplits.com/api/reference/trace-attributes): `POST https://api.trailsplits.com/valhalla/trace_attributes` - [Snow along a route](https://trailsplits.com/api/reference/snow): `GET https://api.trailsplits.com/tiles/v1/snow/route-forecast` - [Outdoor point-to-point route](https://trailsplits.com/api/reference/route): `POST https://api.trailsplits.com/route/v1` ## Recipes - [An elevation profile for a GPX file](https://trailsplits.com/api/recipes#elevation-profile): Reads a GPX file, asks for its elevation profile, prints climb and descent, and writes distance, height and grade as CSV for any chart library. - [Clean and simplify a GPX file](https://trailsplits.com/api/recipes#clean-simplify): Removes repeated and broken points, then keeps the shape within 10 m with fewer points. Names, waypoints and the climb stay. Writes the new file next to the old one. - [A loop generator in 30 lines](https://trailsplits.com/api/recipes#loop-generator): Takes a start, a distance and a profile, asks for the best loop and three alternatives on signed trails, prints them as a table, and saves them as GeoJSON for any map. - [Race splits for a course](https://trailsplits.com/api/recipes#pace-splits): For a GPX course and a target time, prints a split for every kilometre, slower on the climbs and steep descents, with the terrain of each. Paste it on your wrist or into your watch. - [Weather along a hike](https://trailsplits.com/api/recipes#weather-along): For a GPX file and a departure time, prints the forecast at each point at the hour you reach it, at that point's own height. Missing hours stay missing. - [A map with 3D terrain](https://trailsplits.com/api/recipes#terrain-map): One HTML file: the TrailSplits outdoor map with hillshading and 3D terrain from the TrailSplits elevation tiles. Drag with the right mouse button (or two fingers) to tilt. --- # Conventions - Hosts: `https://api.trailsplits.com` for everything except weather (`https://trailsplits.com/api/weather/forecast`). Both answer browsers (CORS). One server in one region (Helsinki, Finland); no SLA. - Keys: optional. Without a key: 500 credits a day per address, 5 requests/s. With a free key from https://trailsplits.com/api/console: `Authorization: Bearer `, 30,000 credits a month, commercial use with attribution. Secret keys stay on servers; a web page uses a browser key (`ts_pub_…`) limited to its origins. - Coordinates: WGS84. Request bodies and JSON results use [lon, lat] (GeoJSON order). Older query strings use lat,lon (elevation `points`, weather `p`, snow `points`, reverse `lat`/`lon`). Lines in route answers are polyline6 (lat,lon pairs at 1e-6) unless the field is GeoJSON. - Units: metres, seconds (or named `minutes`), °C, mm, km/h, %. Unknown values are null, never 0. - Errors are JSON with a stable code: gateway `{error, status, message, retry_after?}`; /v1 calls `{error: {code, message}}`; legacy /route/v1 `{error, status_code, trailsplits: {code, message}}`. 429 carries Retry-After. A refused call costs no credits. - Attribution: show "© TrailSplits" and the data credits (© OpenStreetMap contributors; the named terrain, imagery or weather source) wherever results are shown. ODbL applies to map data: https://trailsplits.com/api/attribution - Limits: heavy requests (round trips, route analysis, route weather) run 1 at a time without a key or on Free (429 concurrency_limit, Retry-After 2). Area lookups (/pois/v1/bbox, /trails/v1/bbox) have a daily ceiling per project of three times the plan's daily average credits (3,000 on Free), then 429 bbox_daily_ceiling. - Versions: answers name their schema (route/1, roundtrips/1, …). New fields can appear; ignore what you don't use. Breaking changes get a new schema version and are announced on the changelog, normally 30 days ahead. - Caching: follow Cache-Control. Computed answers may be kept up to 30 days to serve your users. - Map display: style `https://api.trailsplits.com/styles/current/trailsplits_openmaptiles_style.json` (MapLibre GL JS 5 with the pmtiles protocol); terrain `https://api.trailsplits.com/tiles/v1/terrainrgb/current/{z}/{x}/{y}.png` (raster-dem, encoding mapbox, maxzoom 12). Tiles, styles and terrain need no key and are not counted. # Operations ## Tile gateway health `GET https://api.trailsplits.com/healthz` · not counted · https://trailsplits.com/api/reference/health Public edge observation: text ok, not JSON. Origin paths/handlers can differ; health does not establish dataset freshness. Example: ```bash curl -sS 'https://api.trailsplits.com/healthz' ``` ## Published artifact identities `GET https://api.trailsplits.com/pmtiles/current/manifest.json` · not counted · https://trailsplits.com/api/reference/manifest Resolve each artifact independently. Only the three demo-required artifact shapes are specified here; additional artifacts/metadata are retained. Missing required artifacts prevent this integration. Null/absent source dates stay unknown; retained URLs may retire. Response (Manifest): - `artifacts`: object Example: ```bash curl -sS 'https://api.trailsplits.com/pmtiles/current/manifest.json' ``` ## Artifact status verdict `GET https://api.trailsplits.com/tiles/v1/status` · not counted · https://trailsplits.com/api/reference/status artifact-status/1 producer verdict. Source-bound fresh can have a null source date; available=false is an unavailable manifest, not proof of freshness. Unknown is distinct from stale. Response (Status): - `schema`: "artifact-status/1" - `checked_at`: string - `available`: boolean - `stale`: array of string - `artifacts`: object Example: ```bash curl -sS 'https://api.trailsplits.com/tiles/v1/status' ``` Errors: 500, plus the gateway's 401, 429 and 503. ## Terrain pixel sampling `GET https://api.trailsplits.com/tiles/v1/elevation/{release}/sample` · key optional, counted in credits · https://trailsplits.com/api/reference/elevation Pipe-separated lat,lon. Producer accepts up to 2048 points, clamps requested zoom to available backing data and returns one modelled metre height per point. null is missing, never zero. Does not densify or smooth a line. Example consumers impose smaller bounds. Unknown accuracy/source resolution is not surveyed truth. Parameters: - `release` (path, required): string. Published terrain version, or mutable current. Demo resolves/pins a version. - `points` (query, required): string. 1–2048 lat,lon pairs separated by |; example uses 8/16/32. - `z` (query): integer. Requested zoom; response z is the effective zoom. - `encoding` (query): "mapbox" | "terrarium". Use mapbox for the published terrain. Other encoding is compatibility input, not a second dataset. Response (Elevation): - `release`: string - `z`: integer - `z_requested`: integer - `encoding`: "mapbox" | "terrarium" - `points`: array of object Example: ```bash curl -sS 'https://api.trailsplits.com/tiles/v1/elevation/current/sample?points=46.5369000%2C12.1352000%7C46.5367714%2C12.1348857%7C46.5366429%2C12.1345714%7C46.5365143%2C12.1342571%7C46.5363857%2C12.1339429%7C46.5362571%2C12.1336286%7C46.5361286%2C12.1333143%7C46.5360000%2C12.1330000&z=12&encoding=mapbox' ``` Errors: 400, 404, 500, plus the gateway's 401, 429 and 503. ## Point and batch weather forecast `GET https://trailsplits.com/api/weather/forecast` · not counted · https://trailsplits.com/api/reference/weather Served from the main site, not the API host. Daily values per point and local date; each day names its model and issued_at is the model run (null with a reason when unknown, never the fetch time). Missing values are null, never 0; fewer days or fields than asked is state partial with reasons. Pass each point height: a model cell height can be a thousand metres off in the mountains. Attribution from provider.attribution (Open-Meteo, CC BY 4.0) is required wherever values are shown. Parameters: - `p` (query, required): string. 1–12 points as lat,lon[,elevation_m] separated by ;. Coordinates are rounded to 2 decimals (about 1 km) and echoed rounded. - `d` (query): integer. Forecast days. - `fl` (query): 1. Add the hourly freezing level. - `h` (query): 1. Add hourly[] per point over the requested days (about 130 bytes per hour and point; ask only for the days you need). Any other value is 400. Response (WeatherForecast): - `schema`: "weather-forecast/1" - `provider`: object - `fetched_at`: string - `issued_at`: string or null. The model run, or null with issued_at_reason. Never the fetch time. - `issued_at_reason`: string or null - `fresh_until`: string - `requested_days`: integer - `points`: array of WeatherPoint Example: ```bash curl -sS 'https://trailsplits.com/api/weather/forecast?p=46.0207,7.7491,1608&d=2' ``` Errors: 400, 503, plus the gateway's 401, 429 and 503. ## Analyse a route you already have `POST https://api.trailsplits.com/v1/route-analysis` · key optional, counted in credits · https://trailsplits.com/api/reference/route-analysis Send a GPX or GeoJSON line (at most 1 MB, 5,000 points, 300 km). The answer places it on the mapped network and says on what surface it runs, which OpenStreetMap grades are tagged, how much it climbs (method named), how long a hike takes with the model scope, and every unknown. The line you send is never rewritten, and nothing is drawn across a gap. Untagged paths are unknown, never easy. No geometry is kept after the answer. Request body (RouteAnalysisInput): - `gpx`: string. A GPX 1.0/1.1 document; each trkseg (else rte) is a part. Exactly one of gpx and geometry. - `geometry`: object. GeoJSON LineString, MultiLineString, a Feature of either, or a FeatureCollection of them; a third coordinate is a height. - `profile`: "hike" | "run" | "bike", default "hike" - `elevation`: "auto" | "recorded" | "terrain", default "auto" Response (RouteAnalysis): - `schema`: "route-analysis/1" - `engine`: object - `profile`: string - `input`: object - `match`: object - `surfaces`: object or null - `ways`: object or null - `difficulty`: object or null. Tagged sac_scale per length; untagged paths are unknown, never easy. - `elevation`: object or null - `time`: object or null. Hike only: trailsplits-hike/2 moving time, its scope and outside_calibration; null for run and bike. - `unknowns`: array of object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/route-analysis' -H 'Content-Type: application/json' --data '{"geometry":{"type":"LineString","coordinates":[[12.1352,46.5369],[12.133,46.536]]},"profile":"hike"}' ``` Errors: 400, 413, 422, plus the gateway's 401, 429 and 503. ## Round trips from a start point `POST https://api.trailsplits.com/v1/roundtrips` · key optional, counted in credits · https://trailsplits.com/api/reference/roundtrips Loops that start and end at a point, from the same engine as the TrailSplits Planner. Each loop reports its target error, the distance walked twice and the same facts as route analysis. Status complete means within 10 % of the target with at most 20 % repeated; rough is answered with a reason; when no good loop exists the answer is 422 no_loop, never an invented loop. Limits: 0.5–40 km on foot, 0.5–60 km by bike (422 too_long), up to 3 alternatives. Request body (RoundtripsInput): - `start` (required): array of number. [lon, lat] - `distance_km`: number. 0.5–40 km for hike and run, to 60 km for bike. Or distance_m, not both. - `distance_m`: number - `profile`: "hike" | "run" | "bike", default "hike" - `direction`: number or null - `alternatives`: integer, default 0 - `max_sac`: integer, default 3 - `avoid_paved`: boolean - `hills`: number - `bicycle_type`: "road" | "hybrid" | "mountain" Response (Roundtrips): - `schema`: "roundtrips/1" - `engine`: object - `status`: "complete" | "rough" - `reason`: string or null - `request`: object - `loops`: array of object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/roundtrips' -H 'Content-Type: application/json' --data '{"start":[7.7491,46.0207],"distance_km":12,"profile":"hike"}' ``` Errors: 400, 422, plus the gateway's 401, 429 and 503. ## Weather along a route at arrival time `POST https://api.trailsplits.com/v1/route-weather` · key optional, counted in credits · https://trailsplits.com/api/reference/route-weather Send one line, a departure with its offset and a pace. Samples every 2 km plus the start, end and marked highs and lows each get the forecast at the hour you reach them, at their own terrain height, with the model named per hour. Missing stays missing (weather null with a reason). Sections give ranges, never a combined safety score. Hike uses the calibrated trailsplits-hike/2 time; run and bike need your own pace. Limits: one part, 5,000 points, 120 km, 1 MB; departure from six hours ago to 16 days ahead. Request body (RouteWeatherInput): - `gpx`: string - `geometry`: object. One part only (a LineString); as for route analysis. Limits: 5,000 points, 120 km, 1 MB. - `departure` (required): string. ISO 8601 with offset, from six hours ago to 16 days ahead. - `profile`: "hike" | "run" | "bike", default "hike" - `pace_factor`: number - `min_per_km`: number - `kmh`: number - `stops`: array of object - `freezing_level`: boolean Response (RouteWeather): - `schema`: "route-weather/1" - `engine`: object - `departure`: object - `profile`: string - `pace`: object - `forecast`: object - `samples`: array of object - `sections`: array of object - `unknowns`: array of object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/route-weather' -H 'Content-Type: application/json' --data '{"geometry":{"type":"LineString","coordinates":[[12.1352,46.5369],[12.133,46.536]]},"departure":"2026-10-03T08:00:00+02:00","profile":"hike"}' ``` Errors: 400, 413, 422, 502, 503, plus the gateway's 401, 429 and 503. ## Elevation profile of a line `POST https://api.trailsplits.com/v1/elevation-profile` · key optional, counted in credits · https://trailsplits.com/api/reference/elevation-profile The series a chart draws for a GPX or GeoJSON line: distance, height, grade and position at each point, as rows under named columns, with ascent, descent, extremes and the steepest stretches read from the same series (method named). Terrain heights by default, or the file's own heights when they pass the check. Each part of a file is its own run; nothing is drawn across a gap. Limits: 5,000 points, 300 km, 1 MB. Request body (ElevationProfileInput): - `gpx`: string - `geometry`: object. A GeoJSON line, as for route analysis. Limits: 5,000 points, 300 km, 1 MB. - `elevation`: "auto" | "recorded" | "terrain", default "auto" - `points`: integer, default 1000. At most this many points in the series. Response (ElevationProfile): - `schema`: "elevation-profile/1" - `engine`: object - `input`: object - `elevation`: object - `series`: object - `unknowns`: array of object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/elevation-profile' -H 'Content-Type: application/json' --data '{"geometry":{"type":"LineString","coordinates":[[12.1352,46.5369],[12.133,46.536]]},"points":50}' ``` Errors: 400, 413, 422, plus the gateway's 401, 429 and 503. ## Route through waypoints `POST https://api.trailsplits.com/v1/route` · key optional, counted in credits · https://trailsplits.com/api/reference/waypoint-route A walking, running or cycling route through 2 to 100 waypoints, on signed trails by default. Returns the route as a GeoJSON line with its legs and the same facts as round trips and route analysis: climb, the hike time with its scope, surfaces, ways, SAC grades, fords and aided passages, turn-by-turn steps and every unknown. The same router as the Valhalla-compatible POST /route/v1, in the /v1 style: [lon, lat] waypoints, flat snake_case settings, errors as { error: { code, message } }. Credits as /route/v1: 1, plus 1 per further 10 waypoints. Request body (WaypointRouteInput): - `waypoints` (required): array of array of number. 2 to 100 points, [lon, lat]. A leg (between two waypoints) may be at most 60 km in a straight line. - `profile`: "hike" | "run" | "bike", default "hike" - `max_sac`: integer, default 3. Hike and run: the hardest SAC grade allowed, 1 (T1) to 6 (T6). Refused for a bike. - `marked_trails`: "strict" | "prefer" | "off", default "prefer". Hike and run: how strongly to keep to waymarked trails. Refused for a bike. - `avoid_paved`: boolean, default false - `hills`: number, default 0. From -1 (avoid climbing) to 1 (seek it). - `shortest`: boolean, default false. The shortest route under the other settings, not the most suitable. - `bicycle_type`: "road" | "hybrid" | "mountain". Bike only. - `straight_legs`: array of integer. Leg numbers, counted from 0 (leg 0 joins waypoint 0 to waypoint 1), to draw straight, on no mapped path. - `snap_radius_m`: number. How far a waypoint may lie from a usable path. Response (WaypointRoute): - `schema`: "route/1" - `engine`: object - `status`: "complete" | "best_effort". complete: every leg is a proven cheapest path under the settings. best_effort: a leg hit the search budget (a warning says which). - `settings`: object. What was applied, in the request’s words. - `settings_defaulted`: array of string - `distance_m`: number - `ascent_m`: number - `descent_m`: number - `ascent_method`: string - `time`: object or null. Hike only: moving time from the calibrated trailsplits-hike/2 model, with its scope and error. Null for run and bike. - `surfaces`: object. Paved, gravel, trail and unknown metres; mapped or assumed from the kind of way; stretches placed along the route. - `hazards`: array of object - `ways`: object - `difficulty`: object or null. SAC-tagged metres by grade, untagged path and road metres, and the hardest stretch. - `warnings`: array of object - `unknowns`: array of object - `geometry`: object - `legs`: array of object - `snaps`: array of object - `maneuvers`: array of object - `cap_alternative`: object or null. When the grade cap was left at its default and a harder path is much shorter: its max_sac, distance, climb and minutes. Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/route' -H 'Content-Type: application/json' --data '{"waypoints":[[7.7475,46.0207],[7.7174,46.0091],[7.7355,46.0036]]}' ``` Errors: 400, 413, 422, 503, plus the gateway's 401, 429 and 503. ## Pace plan for a course `POST https://api.trailsplits.com/v1/pace-plan` · key optional, counted in credits · https://trailsplits.com/api/reference/pace-plan Splits every kilometre or mile for one course, from a target time or your pace on the flat: slower where it climbs or descends steeply (effort km = distance km + climb m / 100 + descent m / 300). Each split ends on its mark and names its terrain (steady climb, technical descent, …). Uses the file’s own heights, or the terrain model’s. The engine of the Pace & Split Calculator. A pacing rule, not a physiological model. 2 credits. Request body (PacePlanInput): - `gpx`: string. One line as GPX. Or geometry, not both. Limits: 5,000 points, 300 km, 1 MB; one part only. - `geometry`: object. A GeoJSON LineString, [lon, lat, height?]. - `target_time`: string. h:mm:ss, mm:ss or minutes, e.g. "2:30:00". One of target_time, target_seconds or flat_pace_s_per_km. - `target_seconds`: number - `flat_pace_s_per_km`: number. Your pace on level ground, seconds per km. - `unit`: "km" | "mi", default "km". Splits every kilometre or mile; paces are always per kilometre. Response (PacePlan): - `schema`: "pace-plan/1" - `engine`: object - `settings_defaulted`: array of string - `unit`: "km" | "mi" - `target`: object - `course`: object - `effort`: object - `splits`: array of object - `unknowns`: array of object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/pace-plan' -H 'Content-Type: application/json' --data '{"geometry":{"type":"LineString","coordinates":[[7.7475,46.0207],[7.74,46.018],[7.73,46.014],[7.7174,46.0091]]},"target_time":"0:20:00"}' ``` Errors: 400, 413, 422, plus the gateway's 401, 429 and 503. ## Clean a GPX file `POST https://api.trailsplits.com/v1/gpx/clean` · key optional, counted in credits · https://trailsplits.com/api/reference/gpx-clean Removes repeated points and points with broken coordinates, and optionally every time stamp and sensor extension (heart rate, cadence, power). The file comes back as the file: only what was removed is cut; names, comments, CDATA, waypoints and indentation stay byte for byte. A report says what changed. Body at most 5 MB, 50,000 points. Request body (GpxCleanInput): - `gpx` (required): string. The GPX document. Body at most 5 MB, 50,000 points. - `dedupe`: boolean, default true - `drop_bad`: boolean, default true - `strip_times`: boolean, default false - `strip_extensions`: boolean, default false Response (GpxClean): - `schema`: "gpx-clean/1" - `engine`: object - `settings`: object - `report`: object - `gpx`: string. The cleaned file; byte for byte the input where nothing was removed. Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/gpx/clean' -H 'Content-Type: application/json' --data '{"gpx":"Example1183118311901195"}' ``` Errors: 400, 413, 422, plus the gateway's 401, 429 and 503. ## Simplify a GPX file or line `POST https://api.trailsplits.com/v1/gpx/simplify` · key optional, counted in credits · https://trailsplits.com/api/reference/gpx-simplify Fewer points with the shape kept within a tolerance in ground metres (or a point budget), the climb protected and stops (waypoints, pauses of 10 minutes or more) kept in place. Reports the largest deviation and the distance and climb before and after. Answers equal the TrailSplits GPX Simplifier. Body at most 5 MB, 50,000 points. Request body (GpxSimplifyInput): - `gpx`: string - `geometry`: object - `tolerance_m`: number, default 10 - `max_points`: integer. Budget mode instead of tolerance_m. - `keep_climb`: boolean, default true - `keep_stops`: boolean, default true - `indices`: boolean, default false Response (GpxSimplify): - `schema`: "gpx-simplify/1" - `engine`: object - `settings`: object - `input`: object - `result`: object - `gpx`: string - `geometry`: object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/v1/gpx/simplify' -H 'Content-Type: application/json' --data '{"geometry":{"type":"LineString","coordinates":[[12.1352,46.5369],[12.13509,46.536875],[12.13498,46.53681],[12.13487,46.536785],[12.13476,46.53672],[12.13465,46.536695],[12.13454,46.53663],[12.13443,46.536605],[12.13432,46.53654],[12.13421,46.536515],[12.1341,46.53645],[12.13399,46.536425],[12.13388,46.53636],[12.13377,46.536335],[12.13366,46.53627],[12.13355,46.536245],[12.13344,46.53618],[12.13333,46.536155],[12.13322,46.53609],[12.13311,46.536065],[12.133,46.536]]},"tolerance_m":10}' ``` Errors: 400, 413, 422, plus the gateway's 401, 429 and 503. ## Place search `GET https://api.trailsplits.com/search/v1/forward` · key optional, counted in credits · https://trailsplits.com/api/reference/search Places and signed trails by name: towns, peaks, passes, lakes, huts, stations and more. A bias_bbox lifts places in the map view without excluding others. Misspellings and German ue/oe/ae spellings are found when nothing matches exactly (match: fuzzy). Coordinates are [lon, lat]. Not an address geocoder. Parameters: - `q` (query, required): string. The name, at least 2 letters. "Name, Place" searches near that place; "Place station" finds its railway stations. - `limit` (query): integer. Results. - `bias_bbox` (query): string. minLon,minLat,maxLon,maxLat: a soft preference for the map view. - `bbox` (query): string. minLon,minLat,maxLon,maxLat: a hard filter. Response (SearchResult): - `query`: string - `features`: array of Place Example: ```bash curl -sS 'https://api.trailsplits.com/search/v1/forward?q=Matterhorn&limit=3&bias_bbox=7.5,45.9,7.9,46.1' ``` Errors: 400, plus the gateway's 401, 429 and 503. ## Places near a point `GET https://api.trailsplits.com/search/v1/reverse` · key optional, counted in credits · https://trailsplits.com/api/reference/reverse The nearest named places to a point, nearest first, with their distance in metres. Narrow to kinds (for example peak,hut,village) and widen the radius up to 50 km. Parameters: - `lat` (query, required): number. Latitude. - `lon` (query, required): number. Longitude. - `limit` (query): integer. Results. - `radius_m` (query): integer. Search radius in metres. - `kinds` (query): string. Comma-separated kinds to keep. Response (ReverseResult): - `lat`: number - `lon`: number - `radius_m`: number - `features`: array of object Example: ```bash curl -sS 'https://api.trailsplits.com/search/v1/reverse?lat=46.0207&lon=7.7491&limit=3' ``` Errors: 400, plus the gateway's 401, 429 and 503. ## Trail search `GET https://api.trailsplits.com/trails/v1/search` · key optional, counted in credits · https://trailsplits.com/api/reference/trails Signed hiking and cycling routes (OpenStreetMap route relations) by name or reference, with network level, length and bounding box; the geometry is a representative point. Fetch a relation's full line with GET /trails/v1/relation/{osm_id}, or relations in an area with /trails/v1/bbox. Parameters: - `q` (query, required): string. Name or reference. - `limit` (query): integer. Results. - `type` (query): "hiking" | "bicycle". Route type. Response (TrailSearchResult): - `type`: "FeatureCollection" - `query`: string - `features`: array of object Example: ```bash curl -sS 'https://api.trailsplits.com/trails/v1/search?q=Haute+Route&limit=3' ``` Errors: 400, plus the gateway's 401, 429 and 503. ## Outdoor places in an area `GET https://api.trailsplits.com/pois/v1/bbox` · key optional, counted in credits · https://trailsplits.com/api/reference/pois Huts, shelters, water, viewpoints, passes, parking and transport stops in a small box, as flat records (lat, lon, ele and OSM tags), not GeoJSON Features. Each side of the box may be at most 2°. artifact_version names the weekly release used. Parameters: - `min_lat` (query, required): number. South. - `min_lon` (query, required): number. West. - `max_lat` (query, required): number. North. - `max_lon` (query, required): number. East. - `kind` (query): string. Comma-separated kinds, for example hut,shelter,drinking_water. - `limit` (query): integer. Records. Response (PoiBboxResult): - `type`: "FeatureCollection" - `count`: integer - `artifact_version`: string - `features`: array of object Example: ```bash curl -sS 'https://api.trailsplits.com/pois/v1/bbox?min_lat=46&min_lon=7.7&max_lat=46.05&max_lon=7.8&kind=hut&limit=5' ``` Errors: 400, plus the gateway's 401, 429 and 503. ## Static map image `POST https://api.trailsplits.com/static-map/render` · key optional, counted in credits · https://trailsplits.com/api/reference/static-map A PNG of the outdoor map for a box, with an optional GeoJSON line or features drawn on it: for cards, emails and print. Attribution is drawn into the image; keep it visible. Request body (StaticMapInput): - `bbox` (required): array of number. [minLon, minLat, maxLon, maxLat] - `width`: integer - `height`: integer - `geojson`: object - `padding`: number Response: a PNG image. Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/static-map/render' -H 'Content-Type: application/json' --data '{"bbox":[12.13,46.535,12.137,46.538],"width":320,"height":200}' -o map.png ``` Errors: 400, 503, plus the gateway's 401, 429 and 503. ## Map matching and trace attributes `POST https://api.trailsplits.com/valhalla/trace_attributes` · key optional, counted in credits · https://trailsplits.com/api/reference/trace-attributes Matches a line to the mapped network and returns each way it follows with surface (and whether the surface is mapped or a class default), use and SAC grade, plus the matched line per segment, unmatched stretches with reasons, and three lengths. Valhalla-compatible input; up to 2,000 points. Edge lengths are kilometres. For a full analysis with climb, time and unknowns use route analysis. Request body (TraceAttributesInput): - `shape` (required): array of object - `costing` (required): "pedestrian" | "bicycle" - `shape_match`: "map_snap" Response (TraceAttributes): - `edges`: array of object - `shape`: string. Polyline6 of the matched line, only when the match is one segment. - `units`: "kilometers" - `trailsplits`: object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/valhalla/trace_attributes' -H 'Content-Type: application/json' --data '{"shape":[{"lat":46.5369,"lon":12.1352},{"lat":46.536,"lon":12.133}],"costing":"pedestrian","shape_match":"map_snap"}' ``` Errors: 400, 422, plus the gateway's 401, 429 and 503. ## Snow along a route `GET https://api.trailsplits.com/tiles/v1/snow/route-forecast` · key optional, counted in credits · https://trailsplits.com/api/reference/snow Observed and recent snow cover at up to 512 sampled points, with 24 h and 48 h forecasts kept apart. With evidence=1 the answer carries route-snow-evidence/1: per-point class (snow, clear, unknown) and source, coverage, and a short validity. Unknown is never clear; narrow crossings between samples are not assessed. Points are lat,lon. Parameters: - `points` (query, required): string. lat,lon pairs separated by |, at most 512. - `evidence` (query): 1. Add the route-snow-evidence/1 block. - `revision` (query): string. Your geometry version, echoed back (1–64 letters, digits or ._:-). - `segments` (query): string. Start indices of disconnected segments. Response (SnowRoute): - `evidence`: object - `forecast_24h`: object - `forecast_48h`: object Example: ```bash curl -sS 'https://api.trailsplits.com/tiles/v1/snow/route-forecast?points=46.0207,7.7491%7C46,7.7&evidence=1&revision=example1' ``` Errors: 400, 503, plus the gateway's 401, 429 and 503. ## Outdoor point-to-point route `POST https://api.trailsplits.com/route/v1` · key optional, counted in credits · https://trailsplits.com/api/reference/route Verified POST integration subset of Valhalla-compatible input; returns OSRM-shaped output with polyline6, metres and estimated seconds. Walking explicitly sets T3; running uses trailsplits.profile=run, cycling uses bicycle/hybrid. Unsupported car costing returns 400. No failure should become a straight-line route. Engine/settings and tile release are returned provenance, not immutable route replay. The Planner has a different site body/defaults; read /api/route/capabilities on trailsplits.com. Request body (RouteInput): - `locations` (required): array of object - `costing` (required): "pedestrian" | "bicycle" | "auto". auto is included only to document its HTTP 400 refusal. - `trailsplits`: object - `costing_options`: object Response (Route): - `code`: "Ok" - `routes`: array of object - `trailsplits`: object Example: ```bash curl -sS -X POST 'https://api.trailsplits.com/route/v1' -H 'Content-Type: application/json' --data '{"locations":[{"lat":46.5369,"lon":12.1352},{"lat":46.536,"lon":12.133}],"costing":"pedestrian","costing_options":{"pedestrian":{"max_hiking_difficulty":3}}}' ``` Errors: 400, 422, 503, plus the gateway's 401, 429 and 503.