Day outings near a point
POST https://api.trailsplits.com/v1/outings
Checked by a live probe Key optional · 500 credits a day without one 10 credits per call
Where can I walk near here? An ordered list of day outings near a point: TrailSplits’ curated outings (Switzerland and its borders so far) and, when they do not fill the list, round trips built from your point anywhere the map covers. Each outing carries its facts (length, climb, moving time, hardest tagged grade, stops at the ends, mapped water) and the sentences saying why it is listed; with a date, its go window for that day. Every rule you set leaves out what it cannot judge, counted in left_out: unknown never passes as easy. Facts, not a rating: closures, snow and fires are not assessed. 10 credits; heavy.
Example
# Without a key, leave out the Authorization line (500 credits a day per address).
curl --fail-with-body 'https://api.trailsplits.com/v1/outings' \
-H "Authorization: Bearer $TRAILSPLITS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"lat":46.6863,"lon":7.8632,"radius_km":20,"max_hours":4,"max_grade":"T2","car_free":true}' Request body
| Field | Type | Description |
|---|---|---|
latrequired | number | |
lonrequired | number | |
radius_km | number | |
date | string | YYYY-MM-DD, local time at the outings, from today up to 7 days ahead: adds each outing’s go window and orders the list by it. |
start_time | string | Local HH:MM on date (09:00). Only with a date. |
activity | string: hike | run | |
pace_factor | object | Hike only: relaxed (1.25), usual (1), brisk (0.85), or 0.5 to 2. |
min_per_km | number | Run only; needed with max_hours or a date. |
max_hours | number | |
max_km | number | |
max_ascent_m | number | |
max_grade | string: T1 | T2 | T3 | T4 | T5 | T6 | SAC scale. A line it cannot judge (untagged or off mapped paths) is left out, never passed as easy. |
shape | string: loop | a_to_b | any | |
car_free | boolean | A mapped public-transport stop within 500 m of the start. |
water | boolean | Mapped drinking water at least every 8 km. |
generate | boolean | false: curated outings only. |
limit | integer |
Response
| Field | Type | Description |
|---|---|---|
schema | string: outings/1 | |
engine | object | |
request | object | |
settings_defaulted | array | |
ordered_by | array | |
outings | array | |
matched | integer | |
left_out | object | Per rule, how many outings it left out (farther, shape, longer, slower, time_unknown, more_climb, harder, grade_unknown, no_stop_at_start, water_gap, water_unknown). |
generated | object or null | |
index | object | |
note | string | |
unknowns | array |
Full response schema (JSON Schema)
{
"type": "object",
"properties": {
"schema": {
"type": "string",
"enum": [
"outings/1"
]
},
"engine": {
"type": "object"
},
"request": {
"type": "object"
},
"settings_defaulted": {
"type": "array",
"items": {
"type": "string"
}
},
"ordered_by": {
"type": "array",
"items": {
"type": "string"
}
},
"outings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rank": {
"type": "integer"
},
"id": {
"type": "string"
},
"source": {
"type": "string",
"enum": [
"curated",
"generated"
]
},
"kind": {
"type": "string"
},
"name": {
"type": "string",
"nullable": true
},
"url": {
"type": "string",
"nullable": true
},
"shape": {
"type": "string",
"enum": [
"loop",
"a_to_b"
]
},
"start": {
"type": "object",
"additionalProperties": true
},
"finish": {
"type": "object",
"additionalProperties": true
},
"length_m": {
"type": "number"
},
"ascent_m": {
"type": "number",
"nullable": true,
"description": "null when not known, never 0."
},
"moving_minutes": {
"type": "number",
"nullable": true
},
"time_basis": {
"type": "string",
"nullable": true
},
"hardest_grade": {
"type": "string",
"nullable": true
},
"window": {
"type": "object",
"nullable": true,
"additionalProperties": true
},
"notices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
],
"additionalProperties": true
}
},
"why": {
"type": "array",
"items": {
"type": "string"
}
},
"line": {
"type": "string",
"description": "Encoded polyline (precision 6), at most 160 points: send it as geometry to the route brief or route card."
}
},
"required": [
"rank",
"id",
"source",
"shape",
"length_m",
"why",
"line"
]
}
},
"matched": {
"type": "integer"
},
"left_out": {
"type": "object",
"description": "Per rule, how many outings it left out (farther, shape, longer, slower, time_unknown, more_climb, harder, grade_unknown, no_stop_at_start, water_gap, water_unknown)."
},
"generated": {
"type": "object",
"nullable": true
},
"index": {
"type": "object"
},
"note": {
"type": "string"
},
"unknowns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
}
}
},
"required": [
"schema",
"engine",
"request",
"outings",
"matched",
"left_out",
"unknowns"
]
}Errors
400, 422, 503, plus the gateway's 401, 429 and 503 (every error code). A refused request costs no credits.
Contract
Day outings near a point, for a question like "where can I walk near Interlaken on Monday, at most four hours, T2 at most, from a bus stop?". It is made for agents first: one flat request, and an ordered list where each outing carries its facts and says why it is on the list.
Where the outings come from:
-
Curated outings: TrailSplits' published trail pages and its reviewed Swiss day hikes (213 so far). Each has a page at
trailsplits.com/trails/….- Their facts were read once with route analysis and places along the route, and are rebuilt with the weekly map.
- The climb and the signpost time are the ones the page shows.
- They cover Switzerland and its borders so far.
-
Generated round trips: loops from your point, built as
/v1/roundtripsbuilds them. They only fill the list when the curated outings do not, and are never built forshape: a_to_b. They work anywhere the routing map covers. - Not yet: public community routes.
What it is, and what it is not.
- Facts, not a rating. A mapped path or stop is not a promise that it is open or safe. The time is moving time without breaks, and the forecast is model output. Closures, snow on the ground and fires are not assessed: ask the route brief for an outing's line.
-
Unknown never passes as easy. Each rule you set leaves out the outings it cannot judge, and
left_outcounts them by reason:-
max_gradeleaves out a line whose paths are more than 30 % untagged, or that runs 10 % or more off mapped paths: these are the thresholds at which an outing saysdifficulty_untaggedoroff_mapped_paths. A line only on roads and tracks has nothing to grade, and passes. This holds even atT6, because an untagged path's grade is unknown, not easy; -
waterleaves out a line whose water was not assessed; -
max_hoursleaves out a line with no time.
-
- One route, one climb. A curated outing states its page's climb.
-
Times.
-
A hike's time is the TrailSplits hike time model (
trailsplits-hike/2) on the matched line, at yourpace_factor. -
Without the model's time, it is the DIN 33466 signpost rule (
din33466). -
A run's time is your
min_per_kmon the flat (your_pace_flat).
-
A hike's time is the TrailSplits hike time model (
-
With a date, each outing gets its go window for that day and start time: the route windows facts, read at the hour you reach each point.
-
One time per outing: the window is read at the pace that makes its finish the outing's
moving_minutes. - A walk past midnight is measured against the dusk of the evening it starts.
-
One time per outing: the window is read at the pace that makes its finish the outing's
-
Stops are as mapped. A station, halt or lift within 300 m of an end is preferred to a bus stop, and a named stop to an unnamed one. Timetables, last departures and seasonal mountain railways and lifts are not checked (
timetables_not_checked).
Request
A flat object: only lat and lon are required.
{ "lat": 46.6863, "lon": 7.8632, "radius_km": 20, "date": "2026-10-05", "max_hours": 4, "max_grade": "T2", "car_free": true }
| Field | Default | |
|---|---|---|
lat, lon | required | The point to search around (WGS84) |
radius_km | 25 | 1 to 100. An outing is in range when its start is within this distance in a straight line |
date | none | YYYY-MM-DD, local time at the outings, from today up to 7 days ahead. It adds each outing's go window and orders the list by it |
start_time | 09:00 | Local HH:MM on date. Only with a date |
activity | hike | hike or run |
pace_factor | usual | Hike only: relaxed (1.25), usual (1), brisk (0.85), or a number from 0.5 to 2 |
min_per_km | none | Run only: your pace, 2 to 20. Needed for max_hours or a date, since there is no calibrated running time |
max_hours | none | 0.5 to 12 hours of moving time |
max_km | none | 1 to 40 |
max_ascent_m | none | 50 to 5,000 |
max_grade | none | T1 to T6 (SAC hiking scale) |
shape | any | loop, a_to_b or any |
car_free | false | true: a mapped public-transport stop within 500 m of the start. For an A to B, the finish's stop is stated but not required |
water | false | true: mapped drinking water at least every 8 km along the line |
generate | true | false: curated outings only |
limit | 5 | 1 to 10 outings |
caller | Optional label |
Response
200, JSON, x-trailsplits-product: outings/1.
| Field | |
|---|---|
request | Your request, as read, with the defaults filled in. settings_defaulted names the fields you left out |
ordered_by | Without a date: curated_first, distance. With one: no_thunder, dry, daylight, then as without. Outings without a go window come last. The keys are route windows' own: |
-
no_thunder: points with a thunderstorm signal; -
dry: wet points first, then the highest chance of precipitation in bands of 20 %, so 19 % ranks before 24 %; -
daylight: any minute in the dark, in 15-minute steps. |
outings[] | The list, best first (below) | |
|---|---|---|
left_out | Per rule, how many outings it left out. Reasons: farther, shape, longer, slower, time_unknown, more_climb, harder, grade_unknown, no_stop_at_start, water_gap and water_unknown. Each outing counts once, under the first rule it fails | |
generated | null when no round trip was asked. Otherwise `{ asked_km, status: found \ | none, built, kept, reason }`. |
- At most 3 round trips are built: the best loop and two alternatives from other directions.
-
A loop that fails your rules is counted in
left_outand not kept. When none is kept,generated_left_outsays why. |
index | When the curated facts were built, how many outings they hold, and their coverage |
|---|---|
note, unknowns[] | What the list does not assess, and why parts are missing (no_curated_outings_here, no_generated_for_a_to_b, generated_left_out, forecast_unavailable, start_passed, no_outings, timetables_not_checked, …) |
One outing:
| Field | |
|---|---|
rank, id | 1 for the first. The id is stable for a curated outing (trail:<slug>, swiss_hike:<id>) and positional for a generated one (generated:0) |
source, kind | curated (trail, swiss_hike) or generated (roundtrip) |
name, url | The page on trailsplits.com. null for a generated loop |
shape | loop or a_to_b. The direction is the line's own |
start, finish | { name, lon, lat, stop }. name is the end's place name as the page gives it (a trail page's start and end names; a reviewed hike's end stops), or null. stop is the mapped public-transport stop at that end within 1 km ({ name, kind, distance_m }), or null. start.distance_m is from your point |
length_m, ascent_m, descent_m | Climb is null when not known, never 0 |
moving_minutes, time_basis | trailsplits-hike/2, din33466 or your_pace_flat. null when there is no time. A run at your_pace_flat does not slow for climbs |
signpost_minutes | The Swiss signpost time the page shows, when it shows one: a walking time without breaks, usually longer than the model's |
hardest_grade | The hardest SAC grade tagged on the line, or null when none is tagged |
untagged_path_share, road_share, off_mapped_paths_share | Shares of the line, 0 to 1 |
water | { drinking_mapped, longest_without_drinking_water_m } as in route places: mapped, not promised to run |
window | With a date: the go window's facts (departure, finish_local, wet, thunder, temp_c, feels_like_min_c, gust_max_at_highs_kmh, daylight, complete, why). null otherwise |
notices[] | { code, message }: difficulty_t4_or_harder, no_drinking_water_km (8 km or more), off_mapped_paths (10 % or more), difficulty_untagged (30 % or more), rough_loop; and, from the window, thunderstorm_signal, wet, beyond_civil_twilight, window_incomplete |
why[] | Sentences on why it is listed: distance, shape and size, grade, stop, water, and the window's facts |
line | The line as an encoded polyline (precision 6), simplified to at most 160 points. Send it as geometry to the route brief or the route card for the full picture |
Errors
Every refusal also carries docs (this page's code on /api/errors), and field, limit and got, hint, retry_after or request_id where they apply, as the error standard describes (api-errors-v1, since 6 Oct 2026).
JSON, { "error": { "code", "message" } }.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | Not JSON, an unknown field, a value out of range, start_time without a date, min_per_km for a hike or pace_factor for a run |
| 400 | pace_required | A run with max_hours or a date but no min_per_km |
| 400 | invalid_json | The body (or ?json=) is not valid JSON: send one JSON object with Content-Type: application/json |
| 405 | method_not_allowed | Any method but POST (and OPTIONS) |
| 413 | body_too_large | The body is over 1 MB |
| 422 | departure_in_past | A date before today, or a start time already passed at every outing |
| 422 | beyond_forecast | A date more than 7 days ahead |
| 422 | budget_exhausted | A generated loop ran over the job's limit |
| 503 | overloaded, tiles_unavailable | As for round trips |
How it works
-
The curated outings are filtered in memory, nearest first. With a date, the nearest
2 × limitthat match get their go window: one terrain sample and one forecast fetch each, four at a time. Then the list is ordered. -
A generated round trip is asked only when fewer than
limitcurated outings match. Its target length is under your caps: 90 % ofmax_km, or about 3.2 km per hour ofmax_hoursfor a hike, or 12 km. Yourmax_gradecaps the paths it may use (T3 when unset). Its water and start stop come from the places index. - The job is light without a generated loop, and heavy, like round trips, with one.
Versioning
New fields, notices, left_out reasons and sources may appear in outings/1. A change of a rule's meaning or of the order is outings/2.