Weather along a route at arrival time

POST https://api.trailsplits.com/v1/route-weather

Checked by a live probe Key optional · 500 credits a day without one Heavy: 1 at a time on Free

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.

Example

Weather along a route at arrival time (curl)
# Without a key, leave out the Authorization line (500 credits a day per address).
curl --fail-with-body 'https://api.trailsplits.com/v1/route-weather' \
  -H "Authorization: Bearer $TRAILSPLITS_API_KEY" \
  -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"}'

Sends a real request, with your key or the keyless allowance.

Request body

FieldTypeDescription
gpxstring
geometryobjectOne part only (a LineString); as for route analysis. Limits: 5,000 points, 120 km, 1 MB.
departurerequiredstringISO 8601 with offset, from six hours ago to 16 days ahead.
profilestring: hike | run | bike
pace_factornumber
min_per_kmnumber
kmhnumber
stopsarray
freezing_levelboolean

Response

FieldTypeDescription
schemastring: route-weather/1
engineobject
departureobject
profilestring
paceobject
forecastobject
samplesarray
sectionsarray
unknownsarray
Full response schema (JSON Schema)
{
  "type": "object",
  "properties": {
    "schema": {
      "type": "string",
      "enum": [
        "route-weather/1"
      ]
    },
    "engine": {
      "type": "object"
    },
    "departure": {
      "type": "object",
      "properties": {
        "given": {
          "type": "string"
        },
        "utc": {
          "type": "string"
        }
      },
      "required": [
        "given",
        "utc"
      ]
    },
    "profile": {
      "type": "string"
    },
    "pace": {
      "type": "object"
    },
    "forecast": {
      "type": "object",
      "properties": {
        "schema": {
          "type": "string"
        },
        "provider": {
          "type": "object",
          "properties": {
            "attribution": {
              "type": "string"
            },
            "licence": {
              "type": "string"
            }
          },
          "required": [
            "attribution"
          ]
        },
        "issued_at": {
          "type": "string",
          "nullable": true
        }
      },
      "required": [
        "provider"
      ]
    },
    "samples": {
      "type": "array",
      "minItems": 2,
      "items": {
        "type": "object",
        "properties": {
          "at_m": {
            "type": "number"
          },
          "kind": {
            "type": "string",
            "enum": [
              "start",
              "end",
              "high",
              "low",
              "along"
            ]
          },
          "minutes": {
            "type": "number"
          },
          "arrival_utc": {
            "type": "string"
          },
          "arrival_local": {
            "type": "string"
          },
          "forecast_elevation_m": {
            "type": "number",
            "nullable": true
          },
          "weather": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The forecast hour reached; each hour names its model. Null with missing when the forecast does not hold it."
          },
          "missing": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "at_m",
          "kind",
          "minutes",
          "arrival_utc",
          "weather",
          "missing"
        ]
      }
    },
    "sections": {
      "type": "array",
      "items": {
        "type": "object"
      }
    },
    "unknowns": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "message"
        ]
      }
    }
  },
  "required": [
    "schema",
    "engine",
    "departure",
    "forecast",
    "samples",
    "sections",
    "unknowns"
  ]
}

Errors

400, 413, 422, 502, 503, plus the gateway's 401, 429 and 503 (error conventions). A refused request costs no credits.

Contract

Give a line, a departure and a pace. You get the forecast at the hour you reach each part of the line, each place at its own height. Each hour names the weather model behind it. Missing values stay missing. There is no combined safety score: each section gives its ranges and you decide.

Rules it keeps

  • Each sample at its own height. The forecast is asked for each sample's terrain height (to 10 m), so a pass is colder than the valley below it. forecast_elevation_m says which height the forecast used.
  • The hour you get there. Arrival is the departure plus the moving time to that point, plus your stops before it. The forecast hour read is the nearest one (weather.hour_local), in the place's own time.
  • Moving time.
    • hike: the calibrated trailsplits-hike/2 model (its scope is in pace.applies), times pace_factor.
    • run: your pace on the level, slowed on climbs and steep descents in the proportions of the Swiss signpost slope curve (pace.kind: scaled, pace_scaled). This is not calibrated for running.
    • bike: your pace with no slope effect (pace_flat). No breaks unless you give stops. Past 12 hours of moving, long_walk says so: ask each day of a several-day walk with its own departure.
  • Missing stays missing. An hour the forecast does not hold gives weather: null with missing: beyond_forecast_horizon, hour_missing or point_missing. A value the forecast lacks is null, never 0 (fields_missing).
  • One line. A file of several parts (days) is refused (several_parts): ask one part at a time.
  • No score. sections give ranges and maxima between the line's marked points. They never give one verdict.

Request

{ "gpx": "<gpx…>", "departure": "2026-10-03T08:00:00+02:00", "profile": "hike", "stops": [{ "at_km": 6, "minutes": 30 }] }
{ "geometry": { "type": "LineString", "coordinates": [[lon, lat], …] }, "departure": "2026-10-03T06:00:00Z", "profile": "run", "min_per_km": 6 }
Field
gpx or geometryOne of them, as for route analysis; one part only. Limits: 5,000 points, 120 km, 1 MB
departureISO 8601 with offsetWhen you start, e.g. 2026-10-03T08:00:00+02:00 or …Z. From six hours ago to 16 days ahead
profilehike \run \bikeDefault hike
pace_factor0.5 to 2hike only: 0.8 walks a fifth faster than the model, 1.5 half again as slow; default 1
min_per_km or kmhnumberrun and bike: required, one of the two (1 to 60 min per km)
stopsup to 20 { at_km, minutes }Breaks; each delays every point after it
freezing_levelbooleanAdds weather.freezing_level_m (GFS) per sample; default false
callerstringOptional label for logs and receipts

How samples are chosen

  • Where: every 2 km along the line, at the start and the end, and at the line's marked highs and lows.
    • A high is the top of a climb and a low the bottom of a descent: a turning point of the smoothed heights with at least 100 m of fall or rise on both sides. It is not necessarily the line's highest or lowest point; the end can be higher.
    • An every-2-km sample within 300 m of a marked one is left out.
    • At most 60 samples: on a long line the 2 km spacing grows until the plan fits, and the line is never cut short. Only if the marked highs and lows alone exceed 60 are they thinned evenly (samples_thinned).
  • Heights: the terrain model's, like the rest of the platform. When the terrain model does not answer, the file's own heights are used (terrain_unavailable). With neither, the forecast uses its grid height (heights_unknown).
  • Forecast calls: samples are grouped as the producer rounds points (2 decimals, height to 10 m), 12 to a call, and the calls run in parallel inside the job's deadline.

Response

FieldMeaning
schemaroute-weather/1
settings_defaultedprofile, pace_factor when not given
departuregiven and utc
profile, pacepace.kind model (model, factor, applies), scaled or flat (min_per_km); moving_only: true
stopsAs used, in metres
linepoints, length_m, heights (terrain \recorded \none), terrain_release
forecastschema, provider (id, model, attribution, licence), runs, issued_at, issued_at_reason, fetched_at, fresh_until, days asked, calls
samples[]at_m, kind (start \end \high \low \along), lon, lat, elevation_m, minutes (after departure), arrival_utc, arrival_local (with offset), forecast_elevation_m, weather (below) or null, missing
unknowns[]code, message: pace_scaled, pace_flat, samples_thinned, long_walk, terrain_unavailable, heights_unknown, samples_without_weather, fields_missing

Attribution: show forecast.provider.attribution (Open-Meteo, CC BY 4.0) with the values.

Errors

{ "error": { "code", "message" } }.

HTTPcodeWhen
400invalid_requestNot JSON, unknown field, both or neither of gpx/geometry, departure without an offset, a pace field that does not fit the profile, a bad stop
400pace_requiredrun or bike without min_per_km or kmh
400invalid_gpx, invalid_coordinatesAs for route analysis
405method_not_allowedAny method but POST; the answer carries Allow: POST
413body_too_large, too_many_pointsOver 1 MB, over 5,000 points
422too_longOver 120 km
422several_partsMore than one part (track segment, line)
422departure_in_past, beyond_forecastMore than six hours ago, or more than 16 days ahead
422no_lineNo part of two or more points
422budget_exhaustedThe job's 25 s budget ran out
502forecast_failedThe forecast answered in error or in an unexpected shape
503forecast_unavailableThe forecast has nothing current; Retry-After
503overloadedThe worker's heavy-job queue is full

Versioning

Additive fields may appear in route-weather/1. A change of meaning or a removed field is route-weather/2.

JSON Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://trailsplits.com/schemas/route-weather-1.request.json",
  "type": "object",
  "additionalProperties": false,
  "required": ["departure"],
  "oneOf": [{ "required": ["gpx"] }, { "required": ["geometry"] }],
  "properties": {
    "gpx": { "type": "string" },
    "geometry": { "type": "object", "required": ["type"] },
    "departure": { "type": "string" },
    "profile": { "enum": ["hike", "run", "bike"] },
    "pace_factor": { "type": "number" },
    "min_per_km": { "type": "number" },
    "kmh": { "type": "number" },
    "stops": { "type": "array", "items": { "type": "object", "required": ["at_km", "minutes"] } },
    "freezing_level": { "type": "boolean" },
    "caller": { "type": "string" }
  }
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://trailsplits.com/schemas/route-weather-1.response.json",
  "type": "object",
  "required": ["schema", "engine", "settings_defaulted", "departure", "profile", "pace", "stops", "line", "forecast", "samples", "sections", "unknowns"],
  "properties": {
    "schema": { "const": "route-weather/1" },
    "departure": { "type": "object", "required": ["given", "utc"] },
    "profile": { "enum": ["hike", "run", "bike"] },
    "pace": { "type": "object", "required": ["kind", "moving_only"], "properties": { "kind": { "enum": ["model", "scaled", "flat"] } } },
    "line": { "type": "object", "required": ["points", "length_m", "heights", "terrain_release"], "properties": { "heights": { "enum": ["terrain", "recorded", "none"] } } },
    "forecast": { "type": ["object", "null"], "required": ["schema", "provider", "runs", "issued_at", "fetched_at", "fresh_until", "days", "calls"] },
    "samples": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["at_m", "kind", "lon", "lat", "elevation_m", "minutes", "arrival_utc", "arrival_local", "forecast_elevation_m", "weather", "missing"],
        "properties": {
          "kind": { "enum": ["start", "end", "high", "low", "along"] },
          "missing": { "enum": [null, "beyond_forecast_horizon", "hour_missing", "point_missing"] }
        }
      }
    },
    "unknowns": { "type": "array", "items": { "type": "object", "required": ["code", "message"] } }
  }
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://trailsplits.com/schemas/route-weather-1.error.json",
  "type": "object",
  "required": ["error"],
  "properties": { "error": { "type": "object", "required": ["code", "message"], "properties": { "code": { "type": "string" }, "message": { "type": "string" } } } }
}