UTC --:--:--
Docs·API reference·POST /v1/score/difficulty

The difficulty atlas

A pure atlas read: no scoring, no weather fetch. For the resolved sub-spot, returns the calibrated intrinsic difficulty of each scored dimension as a function of the dimension's own metric, learned from the operator outcome corpus.

POSThttps://api.goable.io/v1/score/difficulty

Pro plan and above: the endpoint itself is gated, not just a feature within it. Returns 404 NO_DIFFICULTY_ATLAS when the resolver lands above sub-spot level or no atlas row exists yet for that cell (the difficulty atlas is dormant until enough calibrated outcomes accumulate for a spot; expect this on new or low-traffic spots today).

What it does

A wave height that barely registers for an expert can be the discriminating factor between "workable" and "unsafe" for a beginner. This endpoint doesn't score a session: it returns, per dimension (wind, wave, …), a calibrated difficulty curve: at which values of the underlying metric the dimension starts separating skilled from unskilled outcomes, and how sharply (the discrimination parameter). It's an IRT-style (item response theory) read over the difficulty atlas corpus, resolved to the most specific catalog level available for the location.

Request

{
 "activity": "kitesurfing",
 "location": { lat: 36.013, lng: -5.604 }
}

That's the whole request: activity and location only. No window, no skill parameter, the atlas already covers the full difficulty range per dimension.

Response

{
 "resolved": {
 "level": "sub-spot",
 "slug": "tarifa-kitesurfing",
 "sub_spot_slug": "tarifa-balneario",
 "distance_to_sub_spot_m": 340
 },
 "dimensions": [
 {
 "dimension": "wind",
 "grid": [8, 12, 16, 20, 24], // metric values, kn
 "difficulty": [-1.8, -0.6, 0.2, 1.1, 2.3], // logit-scale difficulty per grid point
 "discrimination_a": 1.4,
 "cohort_hash": "sha256:3f9e…",
 "computed_at": "2026-06-29T04:30:00Z"
 }, …
 ]
}

resolved tells you which catalog level the atlas hit, same level/slug shape as calibration_provenance. Each entry in dimensions pairs a grid of metric values with a same-length difficulty array (higher = harder to clear favorably) plus a single discrimination_a: how sharply that dimension separates outcomes near its threshold, a steeper curve means a more decisive cutoff. cohort_hash is the SHA-256 provenance of the exact outcome cohort the curve was fit on, and computed_at is when the nightly job produced it.

rider_skill_level vs. this endpoint

Don't confuse this atlas endpoint with the simpler skill mechanism on POST /v1/score: passing an optional rider_skill_level: "beginner" | "intermediate" | "expert" (also Pro+ gated) conditions a single score against that skill tier and returns one score/verdict, same shape as any other /v1/score call. /v1/score/difficulty is a different, heavier surface: it doesn't score anything; it exposes the full calibrated difficulty curve per dimension so you can build your own skill logic (or a UI) on top of it. Use rider_skill_level for "give me one number for this rider"; use the difficulty atlas for "show me the whole curve."

Use cases

  • Booking filter UI: plot the difficulty curve per dimension so a user can see exactly where their skill sits relative to today's forecasted metric values.
  • School logistics: flag which dimension is the discriminating one today and route beginners to a sub-spot where that dimension reads lower on the curve.
  • Risk-tiered pricing: feed discrimination_a and the grid position into your own premium model, where a sharply discriminating, high-difficulty day carries more risk than a flat, easy one.