Skip to content

Watches v1

GET · POST · PATCH · DELETE https://api.trailsplits.com/v1

Needs your API key

Store a route and a few dates; TrailSplits checks it every few hours and calls your webhook when the best window or a thunderstorm signal changes. Facts and their changes, never “safe”. Manage watches and the signing secret in the API console.

Contract

  • Consumers:

A watch is a stored route and date range that TrailSplits re-evaluates on a timer. When what matters changes, it tells you:

  • a webhook to your server for API projects;
  • a push for TrailSplits users (the first-party bridge).

It reports facts and their changes, never "safe" or "go".

Authentication

Like GET /v1/usage (api-keys-usage-v1):

  • A secret API key: the project is the key's own. Publishable (ts_pub_) keys get 403 scope_denied: watches carry a webhook and stored geometry, so they are server-side only.
  • The owner's JWT plus ?project=prj_…: this is what the console uses.

Every answer has Cache-Control: private, no-store.

Objects

Watch

{
  "id": "wch_…",
  "name": "Tour du Mont Blanc stage 3",
  "status": "active",
  "from": "2026-10-04",
  "to": "2026-10-10",
  "timezone": "Europe/Zurich",
  "start_times": ["06:00", "07:00", "08:00"],
  "events": ["window_changed", "thunderstorm_signal", "closure", "snow"],
  "delivery": { "kind": "webhook", "url": "https://example.com/hooks/trailsplits" },
  "line": { "points": 1840, "length_km": 18.4 },
  "metadata": { "trip": "abc" },
  "created_at": "2026-10-03T08:00:00Z",
  "ends_at": "2026-10-11T00:00:00Z",
  "last_evaluated_at": "2026-10-03T09:00:00Z",
  "next_evaluation_at": "2026-10-03T12:00:00Z",
  "last_state": { "best_window": { "date": "2026-10-05", "start": "07:00" }, "flags": ["thunderstorm_signal"] }
}
  • status is active, paused, ended (past to) or failing (webhook deliveries keep failing; see Deliveries).
  • The geometry itself is never returned. The answer only says it is stored (line.points).

Endpoints

Method and pathBodyAnswer
POST /v1/watches{line, name?, from, to, timezone?, start_times?, pace?, prefer?, events?, webhook_url, metadata?}201 Watch
GET /v1/watches—200 {watches: Watch[], limits: {max_active_watches}}
GET /v1/watches/{id}—200 Watch
POST /v1/watches/{id}/pause and /resume—200 Watch; idempotent
DELETE /v1/watches/{id}—200 {id, status: "deleted"}; the geometry is deleted at once
GET /v1/watches/{id}/deliveries—200 {deliveries: Delivery[]}, the last 30 days
POST /v1/webhook-secret—201 {secret, version, created_at}: creates or rotates the project's signing secret. The secret is shown here only, once
GET /v1/webhook-secret—200 {version, created_at}, or 404 when none exists. Never the secret

Request fields:

  • line: a GeoJSON LineString ([lon, lat]), or {gpx: "<gpx document>"}; at most 5,000 points and 120 km, one part (R15's limits).
  • from and to: dates in timezone, at most 7 days apart and starting within 14 days.
  • start_times: 1–8 local times. Default: 06:00 to 10:00 hourly.
  • pace, prefer: passed to R15 as in its contract.
  • events: a subset of window_changed, thunderstorm_signal, closure, snow. Default: all.
  • webhook_url: https:// only, on port 443 or 8443 (since 6 Oct 2026), public hosts only (no IP literals, loopback, private or link-local addresses, nor TrailSplits' own servers), at most 2,048 characters. Checked when the watch is saved and again at every delivery.
  • metadata: at most 1 KB of JSON, echoed in every event. Never put personal data in it.

Errors

Errors use the shape {error, status, message, limit?} from api-keys-usage-v1:

StatuserrorWhen
400invalid_requesta field fails the rules above; the message names it
403scope_denieda publishable key
404not_foundan unknown watch, or one of another project
409watch_limitthe plan's active watches are in use (limit: "max_active_watches")
409webhook_secret_requiredthe project has no signing secret yet; POST /v1/webhook-secret first
422line_refusedR15 refuses the line (too long, several parts, off any mapped path); the message is R15's

Evaluation

  • Every 3 hours while the watch is active, or every hour in the last 48 hours before from.
  • Each evaluation calls R15 once with the stored line, and compares the result with last_state.

Events:

EventFires when
window_changedthe best window (R15's best.id, <date>T<start> local) is a different one
thunderstorm_signala thunderstorm signal appears in, or clears from, a window that was best
closurelater: an official closure or correction along the line is added or lifted. R15 has no closures producer yet
snowlater: the snow state along the line changes class (snow evidence stays in the route brief for now)
endedto has passed. The watch stops and its geometry is deleted the next day

Webhooks

What is sent: POST to webhook_url, with Content-Type: application/json:

{
  "id": "evt_…",
  "type": "window_changed",
  "watch_id": "wch_…",
  "occurred_at": "2026-10-03T12:00:04Z",
  "data": { "previous": { "date": "2026-10-05", "start": "07:00" }, "current": { "date": "2026-10-06", "start": "06:00" }, "why": ["dry at arrival", "daylight margin 3 h"] },
  "metadata": { "trip": "abc" }
}

Headers:

  • X-TrailSplits-Event-Id: evt_… (the same id on every retry: dedupe on it);
  • X-TrailSplits-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>.

Receivers should reject a t more than 5 minutes old.

Delivery and retries:

  • A 2xx within 10 s is delivered.
  • Otherwise retries follow at 1 min, 5 min, 30 min, 2 h and 6 h. After that the event is failed.
  • 20 failed events in a row set the watch to failing, and it stops evaluating until it is resumed.
  • Redirects are not followed.
  • Per watch, events go out in order.

Deliveries log:

{ "event_id": "evt_…", "type": "window_changed", "at": "…Z", "attempts": 2, "status": "delivered", "response_status": 200 }

status is delivered, retrying or failed. The log keeps no payloads.

Metering

Provisional: the owner sets pricing numbers. These follow the published credit table.

  • Each active watch costs 1 credit per day.
  • Both show under the operation watches in /v1/usage.

Active watches per project:

FreeStarterProBusiness
525100500

Privacy and data

  • The route geometry is stored only while the watch exists. DELETE, or the day after to, removes it.
  • It is never shared or used for anything else. The privacy page says so.
  • Delivery logs keep event ids, types, times and status codes for 30 days, never bodies.