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.
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_aand the grid position into your own premium model, where a sharply discriminating, high-difficulty day carries more risk than a flat, easy one.