Developers

Runner Context API

A read-only, token-authenticated API that exposes a runner's profile, training, health, and lab context for AI tools and third-party apps. It is the public, structured counterpart to the in-app Settings → Runner Context page. No SDK required — a single URL returns markdown or JSON.

Authentication & access

There are no API keys or headers. Access is granted by a per-user token in the URL path:

https://hashiri.ai/api/runner-context/{token}

A runner finds and rotates this token under Settings → Runner Context (rotating invalidates the old URL). A request must satisfy all three of:

  1. The token matches a runner (otherwise 404).
  2. That runner has Runner Context sharing enabled (otherwise 403).
  3. The requested section is allowed by the runner's scopes (below).

The endpoints are reachable by non-browser/server clients, so LLM tools and scripts can fetch them directly.

Scopes

Runners choose, per section, what their context exposes. A disabled section is omitted (the /health sub-resource returns 403). Sensitive sections are opt-in.

ScopeContentsDefault
profileDemographics, HR zones, gear, personal bests (with VDOT)on
trainingThis week, weekly volume, goals, race results, recent runson
healthDaily resting HR, HRV, sleep, respiration, weight, recoveryoff
labsLab-test insights (VO₂max, DEXA, blood work, etc.)off

Runner Context

GET/api/runner-context/{token}

The full context. Markdown by default; opt into JSON with ?format=json. The default never changes, so existing shared URLs keep working.

Query parameters

ParamValuesDefault
formatmarkdown | jsonmarkdown

Markdown (default)

text/plain — an LLM-friendly document. When health is on, the Recent Health section ends with a pointer to the /health endpoint so an LLM can fetch the full history.

curl https://hashiri.ai/api/runner-context/{token}

JSON (?format=json)

Structured fields for the cleanly-typed data, plus context_markdown carrying the complete scoped document (so PRs/VDOT, goals, races, and lab insights are never lost). Sections appear only when their scope is on.

{
  "runner": {
    "username": "kazuki", "units": "metric",
    // present only when the `profile` scope is on:
    "age": 34, "sex": "male", "height_cm": 180, "weight_kg": 66,
    "resting_hr": 42, "max_hr": 184, "lthr": 165,
    "hr_zones": { "zone1_max": 139, ... },
    "running_since": "2025-07", "weekly_frequency": "5"
  },
  "scopes": { "profile": true, "training": true, "health": true, "labs": false },
  "health": {                       // only when the health scope is on
    "recent": [ /* DailyMetric[] */ ],
    "latest_vo2_max": 59.75, "latest_fitness_age": 25,
    "history_url": "https://hashiri.ai/api/runner-context/{token}/health"
  },
  "training": {                     // only when the training scope is on
    "weekly_volume": [ { "week_start": "2026-06-08", "distance_km": 42.1, "runs": 4 } ],
    "recent_runs": [ { "date": "2026-06-11", "name": "...", "distance_km": 10.47,
                       "avg_pace": "4:35", "avg_hr": 149, "vo2_max": 59.75 } ]
  },
  "context_markdown": "## Runner Profile\n...full scoped markdown...",
  "generated_at": "2026-06-13T19:00:00.000Z",
  "source": "https://hashiri.ai"
}

Health time-series

GET/api/runner-context/{token}/health

Paginated daily health metrics, newest first. Requires the health scope (403 if off). Walk into the past with cursor pagination until page.has_more is false.

Query parameters

ParamTypeNotes
limitintegerDefault 90, clamped 1–366
beforedateYYYY-MM-DD cursor — rows strictly before this date
{
  "runner": { "units": "metric" },
  "metrics": [ /* DailyMetric[], up to limit, descending by date */ ],
  "page": {
    "limit": 90, "count": 90, "has_more": true,
    "next_before": "2026-03-15",
    "next": "https://hashiri.ai/api/runner-context/{token}/health?limit=90&before=2026-03-15"
  }
}

To page back through everything, follow page.next until has_more is false.

DailyMetric schema

All fields optional (absent when a day lacks that reading). Sleep durations in hours, HRV in ms, HR in bpm.

date                YYYY-MM-DD   (always present)
resting_heart_rate  bpm
hrv                 ms           (HRV status / RMSSD)
sleep_score         0–100
sleep_duration      hours        (total)
deep_sleep          hours
light_sleep         hours
rem_sleep           hours
awake_duration      hours
respiration_rate    breaths/min  (avg during sleep)
vo2_max             ml/kg/min    (only on days with a reading)
fitness_age         years

Status codes & conventions

CodeMeaning
200OK
400Bad before (not YYYY-MM-DD) — /health only
403Sharing disabled, or required scope off (/health needs health)
404Invalid token
  • Caching: Cache-Control: private, max-age=3600.
  • Units: runner.units reflects the runner's preference, but raw numeric fields (height_cm, distance_km, …) are always metric.
  • Stability: markdown stays the default forever; new shapes are added behind explicit params or sub-resources.