A public, read-only JSON endpoint exposing the full training plan with every layer intact — built for AI agents and integrations.
GET https://app.pheidi.training/api/plans/{publicId}
ShortId (7-char base62, hard to guess) or a user-chosen Slug (human-readable, potentially guessable). Slugs are convenience identifiers and should be treated as non-secret.publicId can read the plan. Only the random ShortId offers any meaningful obscurity; a Slug is effectively public and must not be relied on for privacy. There is no access-control gate and the response contains no PII (no email, name, or contact info)./.well-known/api-catalog (RFC 9727); spec at /.well-known/openapi.jsonThe response is a single JSON object with five top-level sections plus identity fields. Each section corresponds to a real layer in Pheidi's plan model. Understanding the layers is the key to interpreting the data correctly.
publicId — stable identifier for the plandisplayName — human-readable name (e.g. "Marathon – Oct 2026")status — Active, Completed, Archived, or PausedprogressionPattern — how weekly volume progresses (e.g. ThreeUpOneDown)createdDate — ISO 8601 UTC timestampraceGoal — the target
The race the plan is built around. distance is one of FiveK, TenK,
HalfMarathon, FullMarathon. targetTime may be null if the runner
hasn't set a goal time. planWeeks is the total length the plan was generated for.
summary — derived totals
Computed aggregates: totalWeeks, totalMiles, peakWeeklyMiles,
longestRun, completedWorkouts, totalRunWorkouts,
completionPercentage. Recomputed from the workout layer on every request — never stored.
Use these instead of summing yourself.
vacations — time-off layer
Array of vacation periods with startDate, endDate, and loadPercent
(0 = full rest, 80 = light reduction). Workouts inside these date ranges have already had this reduction
applied to targetDistanceMiles in the workout layer below — do not apply it again.
Vacations are shown so an agent can explain why a workout's distance dropped.
redistributions — load redistribution layer
When a vacation or day-off removes miles, those miles can be redistributed across future easy runs.
Each entry records sourceType (Vacation or DayOff),
milesRedistributed, and the scope
(ThisWeek, NextNWeeks, UntilDate, RestOfPlan).
Like vacations, the workouts in weeks already reflect the redistribution.
weeks — the plan itself
Ordered array. Each week has weekNumber, phase
(Base → Build → Peak → Taper), and a
workouts array. The workout objects are the most important part of the response.
date / type / description — basic identitytargetDistanceMiles, targetDuration — what to run. These already reflect every active layer (vacation, injury, override, redistribution).loadMultiplier — distance as a fraction of the long run (1.0 = long run, 0.5 = half the long run)modifier — why this workout looks the way it does: None, Vacation, InjuryReduced, DayOff, UserEdited, UserAddedstatus — Pending, Completed, or SkippedpaceZone — Daniels VDOT zone with min/max pace per mile and Borg RPE range when applicable. May be null for workouts with no running pace zone (rest, strength, cross-training, or user-edited workouts that cleared run-specific fields). Always handle the null case before reading subfields.isRunWalk, runMinutes, walkMinutes — Galloway-style intervals (only set when enabled in user profile)warmUpDuration, coolDownDuration — for quality workoutscompletion — present only when status == "Completed". Contains actualDistanceMiles, actualDuration, actualEffort, feedback (TooEasy/JustRight/Tough/TooHard), etc.override — present only when the user has manually edited this workout. Shows the override values that were applied on top of the generated workout.Pheidi uses non-destructive layered overlays. The server applies layers in this order before serializing the response:
weeks)
The workout fields you see are the final values after all layers are applied.
The vacations, redistributions, and per-workout override fields are
provenance — they tell you why a value differs from what generation alone would have produced.
status == "Pending" and date >= todaycompletion.actualDistanceMiles for the current weekNumberphase == "Taper", then check if any vacations overlap, then look for workout modifier valuespaceZone.minPacePerMile / maxPacePerMile; fall back to paceZone.rpeDescription if pace is nulloverride is non-nullThe endpoint is intentionally read-only and limited to plan data. It does not expose:
404 — no plan with that publicId429 — rate limit exceeded; retry after the next minute window
A ready-to-use agent skill is published at
/.well-known/agent-skills/read-pheidi-plan/SKILL.md.
It teaches a Claude (or any tool-using LLM) how to fetch a plan, interpret every layer, and
answer common runner questions correctly. Discoverable via the api-catalog
linkset (relation https://pheidi.training/rels/agent-skill).