Skip to main content
The AI Coach API lets coaches generate periodized training plans in two steps: first produce a human-readable draft for review, then convert the approved draft into structured workout objects. Both steps offer a synchronous endpoint and a Server-Sent Events (SSE) streaming variant for progressive UI rendering. All AI Coach endpoints require a user bearer token, coach role, and the coach_agent feature enabled on the organization’s plan.

Generate a training plan draft

Produces a human-readable multi-week training plan draft in text form. The coach can review and edit the text before passing it to the generate-plan step.
POST /api/v1/ai-coach/generate-draft
Authentication: Bearer token (coach role + coach_agent feature required)
weeks
integer
required
Duration of the training plan in weeks. Must be between 1 and 52.
level
string
required
Athlete experience level. One of "beginner", "intermediate", or "advanced".
org_id
string
required
Organization ID used to resolve the training principle for the plan.
additional_prompt
string
Free-text instructions to guide the AI (e.g. "Include two track sessions per week").
training_principle
string
Per-request training principle override. When set and non-empty, takes priority over the organization’s stored principle.
start_date
string
ISO 8601 date string for when the plan begins (e.g. "2025-05-05").
workout_preference
string
default:"ai_generate"
How to source workout content:
  • "ai_generate" — always generate new workouts
  • "prefer_existing" — reuse existing organization workouts when available
  • "strict_existing" — only use existing workouts; skip AI generation for gaps
curl -X POST https://api.astral.run/api/v1/ai-coach/generate-draft \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "weeks": 12,
    "level": "intermediate",
    "org_id": "org_xyz789",
    "additional_prompt": "Focus on 5K speed development with one long run per week.",
    "start_date": "2025-05-05",
    "workout_preference": "ai_generate"
  }'
Response 200 OK
{
  "status": "success",
  "data": {
    "draft": "**12-Week 5K Development Plan — Intermediate**\n\nWeek 1: Base building...\nMonday: Easy 8km @ conversational pace\nWednesday: 6 × 400m intervals @ 5K pace...",
    "training_principle": "Polarized training with 80/20 easy-hard split.",
    "metadata": {
      "weeks": 12,
      "level": "intermediate",
      "org_id": "org_xyz789",
      "generated_at": "2025-04-23T10:30:00Z"
    }
  }
}
data.draft
string
Full text of the AI-generated plan draft. Coaches can freely edit this before submitting to generate-plan.
data.training_principle
string
The training principle that was used, resolved from the organization or per-request override.
data.metadata
object
Request parameters echoed back alongside a server-side generated_at timestamp.

Generate structured plan content

Converts a coach-approved draft into an array of structured workout objects, one per training day. Requires the same fields as generate-draft plus the refined_draft the coach approved or edited.
POST /api/v1/ai-coach/generate-plan
Authentication: Bearer token (coach role + coach_agent feature required)
weeks
integer
required
Number of weeks. Must match the draft that was generated.
level
string
required
"beginner", "intermediate", or "advanced".
org_id
string
required
Organization ID.
refined_draft
string
required
The coach-edited draft text. Minimum 10 characters.
additional_prompt
string
Additional guidance for the structured output phase.
training_principle
string
Per-request principle override.
curl -X POST https://api.astral.run/api/v1/ai-coach/generate-plan \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "weeks": 12,
    "level": "intermediate",
    "org_id": "org_xyz789",
    "refined_draft": "**12-Week 5K Development Plan**\n\nWeek 1: Easy base...\nMonday: Easy 8km @ conversational pace\n..."
  }'
Response 200 OK
{
  "status": "success",
  "data": {
    "content": [
      {
        "week": 1,
        "phase": "base",
        "day": "Monday",
        "workout_name": "Easy Base Run",
        "workout_description": "Build aerobic foundation at a comfortable, conversational pace.",
        "workout_type": "run",
        "workout_sub_type": "easy-run",
        "target_duration_in_seconds": 2880,
        "target_distance_in_meters": 8000,
        "content_blocks": [
          { "type": "warmup", "duration_seconds": 600 },
          { "type": "main", "duration_seconds": 2280 }
        ]
      }
    ],
    "weekly_info": {
      "1": {
        "phase": "base",
        "focus": "Aerobic foundation",
        "total_distance_km": 48
      }
    }
  }
}
data.content
array
Array of workout objects, one per training day across all weeks.
data.content[].week
integer
Week number within the plan (1-based).
data.content[].phase
string
Training phase label (e.g. "base", "build", "peak", "taper").
data.content[].workout_sub_type
string
Workout sub-type (e.g. "easy-run", "tempo", "long-run", "intervals").
data.content[].target_duration_in_seconds
integer
Target workout duration in seconds.
data.content[].target_distance_in_meters
integer
Target distance in meters.
data.content[].content_blocks
array
Structured breakdown of the workout into segments (warmup, main, cooldown).
data.weekly_info
object
Per-week metadata keyed by week number string: phase, focus, and total distance.

Stream draft generation (SSE)

Same behavior as POST /generate-draft but delivers real-time progress updates over a Server-Sent Events connection. The connection stays open until the done event is received.
POST /api/v1/ai-coach/generate-draft/stream
Authentication: Bearer token (coach role + coach_agent feature required) Request body: Identical to POST /generate-draft. Response: text/event-stream
data: {"type": "progress", "status": "thinking", "message": "Analyzing training principle...", "progress": 20}

data: {"type": "progress", "status": "thinking", "message": "Generating weekly structure...", "progress": 60}

data: {"type": "result", "data": {"draft": "...", "training_principle": "...", "metadata": {...}}}

data: {"type": "done"}
SSE event types
TypeDescription
progressIntermediate status update. Fields: status, message, progress (0–100).
resultFinal payload. data field mirrors the synchronous 200 response body.
errorGeneration failed. Field: error (string).
doneStream finished. Always the last event.
const response = await fetch('/api/v1/ai-coach/generate-draft/stream', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ weeks: 12, level: 'intermediate', org_id: 'org_xyz789' })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const lines = decoder.decode(value).split('\n');
  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const event = JSON.parse(line.slice(6));
      console.log(event);
    }
  }
}

Stream plan content — incremental (SSE)

Streams structured workout objects one at a time as the AI generates them. Workouts appear progressively in the UI without waiting for the full plan to complete. This endpoint is the preferred streaming variant for the plan editor.
POST /api/v1/ai-coach/generate-plan/stream-content
Authentication: Bearer token (coach role + coach_agent feature required) Request body: Identical to POST /generate-plan (requires refined_draft). Response: text/event-stream
data: {"type": "progress", "status": "generating", "message": "Generating workout 1 of 60", "count": 1, "total": 60, "progress": 2}

data: {"type": "workout", "data": {"week": 1, "day": "Monday", "workout_name": "Easy Base Run", ...}}

data: {"type": "workout", "data": {"week": 1, "day": "Wednesday", "workout_name": "Tempo Run", ...}}

data: {"type": "weekly_info", "data": {"1": {"phase": "base", "focus": "Aerobic foundation", "total_distance_km": 48}}}

data: {"type": "result", "total_workouts": 60, "weekly_info": {...}}

data: {"type": "done"}
SSE event types
TypeDescription
workoutA single generated workout object. data field matches the content[] item schema.
progressCount-based progress update. Fields: status, message, count, total, progress.
weekly_infoEmitted once all workouts for a given week are generated. data is a week-keyed object.
resultFinal summary. Fields: total_workouts (integer), weekly_info (full object).
errorGeneration failed. Field: error (string).
doneStream complete. Always the last event.

Stream plan content — batch (SSE)

Alternative SSE endpoint that accumulates all workouts before emitting a single result event. Use this when progressive display is not needed and you prefer a simpler client implementation.
POST /api/v1/ai-coach/generate-plan/stream
Authentication: Bearer token (coach role + coach_agent feature required) Request body: Identical to POST /generate-plan. SSE event types: Same as the draft stream — progress, result, error, done. The result event data contains the full content array.

Get training principle

Returns the training principle configured for an organization. Coaches can use this to preview what the AI will use before generating a plan.
GET /api/v1/ai-coach/training-principle/{org_id}
Authentication: Bearer token (coach role + coach_agent feature required)
org_id
string
required
The organization whose training principle to retrieve.
curl https://api.astral.run/api/v1/ai-coach/training-principle/org_xyz789 \
  -H "Authorization: Bearer <token>"
Response 200 OK
{
  "status": "success",
  "data": {
    "training_principle": "Polarized training with 80/20 easy-hard split. Emphasize aerobic base in weeks 1-8, introduce intensity in weeks 9-12.",
    "org_id": "org_xyz789"
  }
}
data.training_principle
string
The training principle text. Resolved from the coach’s personal settings first, then the organization’s settings.

Error codes

StatusMeaning
400Validation error — missing/invalid fields, refined_draft too short
401Missing or invalid authentication
403Coach role or coach_agent feature not present on the organization
404Organization not found
500Internal server error during AI generation