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

Day outings near a point (curl)
# 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}'

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

Request body

FieldTypeDescription
latrequirednumber
lonrequirednumber
radius_kmnumber
datestringYYYY-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_timestringLocal HH:MM on date (09:00). Only with a date.
activitystring: hike | run
pace_factorobjectHike only: relaxed (1.25), usual (1), brisk (0.85), or 0.5 to 2.
min_per_kmnumberRun only; needed with max_hours or a date.
max_hoursnumber
max_kmnumber
max_ascent_mnumber
max_gradestring: T1 | T2 | T3 | T4 | T5 | T6SAC scale. A line it cannot judge (untagged or off mapped paths) is left out, never passed as easy.
shapestring: loop | a_to_b | any
car_freebooleanA mapped public-transport stop within 500 m of the start.
waterbooleanMapped drinking water at least every 8 km.
generatebooleanfalse: curated outings only.
limitinteger

Response

FieldTypeDescription
schemastring: outings/1
engineobject
requestobject
settings_defaultedarray
ordered_byarray
outingsarray
matchedinteger
left_outobjectPer 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).
generatedobject or null
indexobject
notestring
unknownsarray
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/roundtrips builds them. They only fill the list when the curated outings do not, and are never built for shape: 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_out counts them by reason:
    • max_grade leaves 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 says difficulty_untagged or off_mapped_paths. A line only on roads and tracks has nothing to grade, and passes. This holds even at T6, because an untagged path's grade is unknown, not easy;
    • water leaves out a line whose water was not assessed;
    • max_hours leaves 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 your pace_factor.
    • Without the model's time, it is the DIN 33466 signpost rule (din33466).
    • A run's time is your min_per_km on the flat (your_pace_flat).
  • 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.
  • 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 }
FieldDefault
lat, lonrequiredThe point to search around (WGS84)
radius_km251 to 100. An outing is in range when its start is within this distance in a straight line
datenoneYYYY-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_time09:00Local HH:MM on date. Only with a date
activityhikehike or run
pace_factorusualHike only: relaxed (1.25), usual (1), brisk (0.85), or a number from 0.5 to 2
min_per_kmnoneRun only: your pace, 2 to 20. Needed for max_hours or a date, since there is no calibrated running time
max_hoursnone0.5 to 12 hours of moving time
max_kmnone1 to 40
max_ascent_mnone50 to 5,000
max_gradenoneT1 to T6 (SAC hiking scale)
shapeanyloop, a_to_b or any
car_freefalsetrue: 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
waterfalsetrue: mapped drinking water at least every 8 km along the line
generatetruefalse: curated outings only
limit51 to 10 outings
callerOptional label

Response

200, JSON, x-trailsplits-product: outings/1.

Field
requestYour request, as read, with the defaults filled in. settings_defaulted names the fields you left out
ordered_byWithout 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_outPer 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
generatednull 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_out and not kept. When none is kept, generated_left_out says why. |
indexWhen 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, id1 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, kindcurated (trail, swiss_hike) or generated (roundtrip)
name, urlThe page on trailsplits.com. null for a generated loop
shapeloop 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_mClimb is null when not known, never 0
moving_minutes, time_basistrailsplits-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_minutesThe Swiss signpost time the page shows, when it shows one: a walking time without breaks, usually longer than the model's
hardest_gradeThe hardest SAC grade tagged on the line, or null when none is tagged
untagged_path_share, road_share, off_mapped_paths_shareShares of the line, 0 to 1
water{ drinking_mapped, longest_without_drinking_water_m } as in route places: mapped, not promised to run
windowWith 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
lineThe 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" } }.

HTTPcodeWhen
400invalid_requestNot 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
400pace_requiredA run with max_hours or a date but no min_per_km
400invalid_jsonThe body (or ?json=) is not valid JSON: send one JSON object with Content-Type: application/json
405method_not_allowedAny method but POST (and OPTIONS)
413body_too_largeThe body is over 1 MB
422departure_in_pastA date before today, or a start time already passed at every outing
422beyond_forecastA date more than 7 days ahead
422budget_exhaustedA generated loop ran over the job's limit
503overloaded, tiles_unavailableAs for round trips

How it works

  • The curated outings are filtered in memory, nearest first. With a date, the nearest 2 × limit that 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 limit curated outings match. Its target length is under your caps: 90 % of max_km, or about 3.2 km per hour of max_hours for a hike, or 12 km. Your max_grade caps 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.