Health Access MCP / Reference / Plans

Reference

Plans and "Put on Watch"

Your AI writes one small JSON file. The app turns it into a custom workout with the step names on your wrist and puts it on the Watch, one tap. When you swim it, the exported session comes back with those names on every interval. This page is the format, the rules and the flow.

The flow

  1. Your AI calls push_plan with the plan below (or writes the file itself, or you run healthaccess plan push session.json). The server validates it, fills id, created_at and schema, and writes plans/<id>.json. The id is the UTC time plus the slug of the name: 2026-05-13T19:02:11Z-s12.
  2. The folder syncs to the phone. The app scans plans/ on launch, when it comes to the foreground and after every export, so the plan shows up within about a minute as "Your AI wrote S27" with Put on Watch and See parts. If the app was not in front, that is also the one notification it sends.
  3. You tap "Put on Watch". Two ways, chosen in Settings:
    • Schedule (the default): WorkoutKit schedules the workout for a date, by default the next day at 10:00, changeable. The Watch shows it in the Workout app at that time, and the app can verify it is scheduled, so the status is a fact. default time to confirm
    • Apple's "Add to Watch" sheet: installs it as a plain custom workout you start whenever you like. Apple's sheet does not report back, so the app asks you whether you added it and records method: preview.
  4. The app writes plans/<id>.status.json: scheduled, failed (with the error text) or cancelled. Your AI reads it with get_plan_status; until the file exists the state is pending.
  5. You do the session. The Watch shows each step's label. HealthKit drops the labels when it saves the workout, but it keeps the WorkoutKit id, so the exported file gets an exact plan_ref and every step gets its label back from the plan.

The format

{
  "name": "S27",                         # shown in the app and on the Watch, 40 characters at most
  "sport": "pool_swim",                  # pool_swim | indoor_row
  "pool_length_m": 50,                   # a reminder for you; the Watch keeps its own setting
  "warmup":   {"goal": {"type": "time_s", "value": 120}, "zone": 1},
  "blocks": [
    {"repeat": 4, "steps": [
      {"kind": "work",     "goal": {"type": "distance_m", "value": 50}, "zone": 1, "label": "FR easy, long strokes"},
      {"kind": "recovery", "goal": {"type": "time_s", "value": 30}}
    ]}
  ],
  "cooldown": {"goal": {"type": "distance_m", "value": 100}, "zone": 1},
  "notes": "what this session is for, shown in the app",
  "scheduled_for": "2026-10-27T07:30:00Z" # optional; also used to match the finished workout
}

Rules the server checks

A full example

The plan behind the example swim on the files page, plans/2026-05-13T19:02:11Z-s12.json in the repository's synthetic example folder: 1,200 m in six parts, about 36 minutes, as push_plan wrote it.

{
  "schema": "healthaccess.plan.v1",
  "id": "2026-05-13T19:02:11Z-s12",
  "name": "S12",
  "sport": "pool_swim",
  "pool_length_m": 50,
  "warmup": {"goal": {"type": "distance_m", "value": 200}, "zone": 1, "label": "FR easy, find your breathing"},
  "blocks": [
    {"repeat": 3, "steps": [
      {"kind": "work", "goal": {"type": "distance_m", "value": 50}, "zone": 1, "label": "DRILL 6-3-6: 6 kicks side, 3 strokes"},
      {"kind": "work", "goal": {"type": "distance_m", "value": 50}, "zone": 1, "label": "FR easy, long strokes"},
      {"kind": "recovery", "goal": {"type": "time_s", "value": 30}}
    ]},
    {"repeat": 4, "steps": [
      {"kind": "work", "goal": {"type": "distance_m", "value": 100}, "zone": 2, "label": "FR 100 steady, breathe every 3"},
      {"kind": "recovery", "goal": {"type": "time_s", "value": 40}}
    ]},
    {"repeat": 2, "steps": [
      {"kind": "work", "goal": {"type": "time_s", "value": 120}, "zone": 1, "label": "KICK on back, no board, hips up"},
      {"kind": "recovery", "goal": {"type": "time_s", "value": 45}}
    ]},
    {"repeat": 1, "steps": [
      {"kind": "work", "goal": {"type": "distance_m", "value": 100}, "zone": 2, "label": "FR 100, carry the drill into it"}
    ]}
  ],
  "cooldown": {"goal": {"type": "distance_m", "value": 100}, "zone": 1, "label": "Easy, any stroke, long exhale"},
  "notes": "Technique week 2. Short drill reps, then four steady 100s to carry the feel over. Kick on back is for the hips, not for speed. Target effort 5 to 6.",
  "created_by": "claude-code",
  "created_at": "2026-05-13T19:02:11Z",
  "scheduled_for": "2026-05-14T05:00:00Z"
}

The swim idea usually puts a two-minute time step first: the athlete starts the workout in the shower, so the Watch is already in swim mode at the pool edge and no length is lost to fumbling with a wet screen. This plan warms up over 200 m instead; both work.

What came back

The app's answer, plans/2026-05-13T19:02:11Z-s12.status.json, after WorkoutKit scheduled it for the next morning (method: schedule; a plan installed through Apple's sheet says preview and has no scheduled_for):

{
  "plan_id": "2026-05-13T19:02:11Z-s12",
  "state": "scheduled",
  "at": "2026-05-13T19:04:40Z",
  "method": "schedule",
  "workoutkit_id": "D4E7A2C1-6F3B-4E8A-9B5C-1A2F3E4D5C6B",
  "scheduled_for": "2026-05-14T05:00:00Z",
  "error": null
}

workoutkit_id is what links the finished swim back to this plan: the exported session on the files page carries plan_ref S12 and every step has its label back. get_plan_status returns the same fields; list_plans returns them for every plan in the folder, with planned distance and time.

Limits, all Apple's