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
# 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"}' Request body
| Field | Type | Description |
|---|---|---|
gpx | string | |
geometry | object | One part only (a LineString); as for route analysis. Limits: 5,000 points, 120 km, 1 MB. |
departurerequired | string | ISO 8601 with offset, from six hours ago to 16 days ahead. |
profile | string: hike | run | bike | |
pace_factor | number | |
min_per_km | number | |
kmh | number | |
stops | array | |
freezing_level | boolean |
Response
| Field | Type | Description |
|---|---|---|
schema | string: route-weather/1 | |
engine | object | |
departure | object | |
profile | string | |
pace | object | |
forecast | object | |
samples | array | |
sections | array | |
unknowns | array |
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_msays 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 calibratedtrailsplits-hike/2model (its scope is inpace.applies), timespace_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 givestops. Past 12 hours of moving,long_walksays 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: nullwithmissing:beyond_forecast_horizon,hour_missingorpoint_missing. A value the forecast lacks isnull, never 0 (fields_missing). -
One line. A file of several parts (days) is refused (
several_parts): ask one part at a time. -
No score.
sectionsgive 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 geometry | One of them, as for route analysis; one part only. Limits: 5,000 points, 120 km, 1 MB | |||
departure | ISO 8601 with offset | When you start, e.g. 2026-10-03T08:00:00+02:00 or …Z. From six hours ago to 16 days ahead | ||
profile | hike \ | run \ | bike | Default hike |
pace_factor | 0.5 to 2 | hike only: 0.8 walks a fifth faster than the model, 1.5 half again as slow; default 1 | ||
min_per_km or kmh | number | run and bike: required, one of the two (1 to 60 min per km) | ||
stops | up to 20 { at_km, minutes } | Breaks; each delays every point after it | ||
freezing_level | boolean | Adds weather.freezing_level_m (GFS) per sample; default false | ||
caller | string | Optional 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
highis the top of a climb and alowthe 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).
-
A
-
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
| Field | Meaning | ||||
|---|---|---|---|---|---|
schema | route-weather/1 | ||||
settings_defaulted | profile, pace_factor when not given | ||||
departure | given and utc | ||||
profile, pace | pace.kind model (model, factor, applies), scaled or flat (min_per_km); moving_only: true | ||||
stops | As used, in metres | ||||
line | points, length_m, heights (terrain \ | recorded \ | none), terrain_release | ||
forecast | schema, 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" } }.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | Not 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 |
| 400 | pace_required | run or bike without min_per_km or kmh |
| 400 | invalid_gpx, invalid_coordinates | As for route analysis |
| 405 | method_not_allowed | Any method but POST; the answer carries Allow: POST |
| 413 | body_too_large, too_many_points | Over 1 MB, over 5,000 points |
| 422 | too_long | Over 120 km |
| 422 | several_parts | More than one part (track segment, line) |
| 422 | departure_in_past, beyond_forecast | More than six hours ago, or more than 16 days ahead |
| 422 | no_line | No part of two or more points |
| 422 | budget_exhausted | The job's 25 s budget ran out |
| 502 | forecast_failed | The forecast answered in error or in an unexpected shape |
| 503 | forecast_unavailable | The forecast has nothing current; Retry-After |
| 503 | overloaded | The 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" } } } }
}