Skip to content

KRD Format Specification

KRD (Kaiord Representation Definition) is a JSON-based canonical format for workout data, inspired by the Garmin FIT protocol but designed for human readability.

MIME type: application/vnd.kaiord+json

Design principles

  • Round-trip safe -- convert FIT/TCX/ZWO to KRD and back without data loss
  • Schema-validated -- all KRD files validate against Zod schemas
  • Normalized -- consistent units and naming across all source formats
  • Extensible -- custom fields via extensions object

Core structure

json
{
  "version": "1.0",
  "type": "workout",
  "metadata": {},
  "sessions": [],
  "laps": [],
  "records": []
}

Required fields

  • version (string): KRD schema version, e.g. "1.0"
  • type (string): "workout", "activity", or "course"
  • metadata (object): file-level metadata

Optional fields

  • sessions (array): training sessions
  • laps (array): lap/interval data
  • records (array): time-series data points
  • events (array): workout events (start, stop, pause)
  • extensions (object): format-specific extensions

Metadata

json
{
  "metadata": {
    "created": "2025-01-15T10:30:00Z",
    "manufacturer": "garmin",
    "product": "fenix7",
    "sport": "running",
    "subSport": "trail"
  }
}

Workout steps

Workouts are stored in extensions.workout.steps:

json
{
  "stepIndex": 0,
  "durationType": "time",
  "duration": { "type": "time", "seconds": 600 },
  "targetType": "heart_rate",
  "target": {
    "type": "heart_rate",
    "value": { "unit": "zone", "value": 2 }
  },
  "intensity": "warmup",
  "notes": "Easy warmup"
}

Duration types

TypeExample
time{ "type": "time", "seconds": 600 }
distance{ "type": "distance", "meters": 5000 }
open{ "type": "open" }
calories{ "type": "calories", "calories": 200 }
power_less_than{ "type": "power_less_than", "watts": 150 }
power_greater_than{ "type": "power_greater_than", "watts": 300 }
heart_rate_less_than{ "type": "heart_rate_less_than", "bpm": 120 }
repeat_until_steps_complete{ "type": "repeat_until_steps_complete", "repeatCount": 5, "repeatFrom": 0 }

Additional repeat types: repeat_until_time, repeat_until_distance, repeat_until_calories, repeat_until_power_less_than, repeat_until_power_greater_than, repeat_until_heart_rate_less_than, repeat_until_heart_rate_greater_than.

Target types

TypeUnits
powerwatts, percent_ftp, zone, range
heart_ratebpm, percent_max, zone, range
pacemeters_per_second, zone, range
cadencerpm, zone, range
openNo target

Sessions

Sessions capture training session summaries:

json
{
  "sessions": [
    {
      "startTime": "2025-01-15T10:30:00Z",
      "totalElapsedTime": 3600,
      "totalTimerTime": 3540,
      "totalDistance": 10000,
      "sport": "running",
      "avgHeartRate": 145,
      "maxHeartRate": 178,
      "avgCadence": 85,
      "avgPower": 250,
      "totalCalories": 650
    }
  ]
}

Key fields: startTime (ISO 8601), totalElapsedTime (seconds), totalTimerTime (active time, excludes pauses), totalDistance (meters), sport.

Laps

Laps represent intervals within a session:

json
{
  "laps": [
    {
      "startTime": "2025-01-15T10:30:00Z",
      "totalElapsedTime": 600,
      "totalDistance": 1000,
      "avgHeartRate": 142,
      "maxHeartRate": 155,
      "avgCadence": 84,
      "avgPower": 245
    }
  ]
}

Records

Time-series data points (typically 1 Hz):

json
{
  "records": [
    {
      "timestamp": "2025-01-15T10:30:00Z",
      "position": { "lat": 41.3851, "lon": 2.1734 },
      "altitude": 12.5,
      "heartRate": 145,
      "cadence": 85,
      "power": 250,
      "speed": 2.78,
      "distance": 100
    }
  ]
}

Fields: timestamp (ISO 8601), position (lat/lon in degrees), altitude (meters), heartRate (bpm), cadence (rpm), power (watts), speed (m/s), distance (cumulative meters). All fields except timestamp are optional.

Units and conventions

MeasurementUnit
Timeseconds
Distancemeters
Speedmeters per second
Altitudemeters
Heart ratebpm
Cadencerpm (running: spm/2)
Powerwatts
TimestampsISO 8601 UTC

Field naming uses camelCase with prefixes: total*, avg*, max*, min*.

Extensions

Format-specific data lives in extensions:

json
{
  "extensions": {
    "fit": { "developerFields": [], "unknownMessages": {} },
    "tcx": { "extensions": {} },
    "zwift": { "originalDurationType": "distance" }
  }
}

FIT developer fields and unknown messages are preserved during round-trip conversions:

json
{
  "extensions": {
    "fit": {
      "developerFields": [
        { "fieldDefinitionNumber": 0, "fieldName": "custom_field", "value": 42 }
      ]
    }
  }
}

Validation rules

  1. All KRD files MUST validate against the Zod schema
  2. Timestamps MUST be in ISO 8601 format with UTC timezone
  3. Numeric values MUST be finite (no NaN or Infinity)
  4. Arrays MUST be sorted by timestamp where applicable
  5. Required fields MUST be present; optional fields may be omitted (not null)

Round-trip tolerances

  • Time: +/- 1 second
  • Power: +/- 1 watt or +/- 1% FTP
  • Heart rate: +/- 1 bpm
  • Cadence: +/- 1 rpm
  • Distance: +/- 1 meter

Example: minimal valid KRD

json
{
  "version": "1.0",
  "type": "workout",
  "metadata": {
    "created": "2025-01-15T10:30:00Z",
    "sport": "running"
  }
}

Example: complete workout

json
{
  "version": "1.0",
  "type": "workout",
  "metadata": {
    "created": "2025-01-15T10:30:00Z",
    "manufacturer": "garmin",
    "product": "fenix7",
    "sport": "cycling",
    "subSport": "indoor_cycling"
  },
  "extensions": {
    "workout": {
      "name": "FTP Intervals",
      "sport": "cycling",
      "steps": [
        {
          "stepIndex": 0,
          "durationType": "time",
          "duration": { "type": "time", "seconds": 600 },
          "targetType": "power",
          "target": {
            "type": "power",
            "value": { "unit": "percent_ftp", "value": 60 }
          },
          "intensity": "warmup",
          "notes": "Easy warmup"
        },
        {
          "stepIndex": 1,
          "durationType": "time",
          "duration": { "type": "time", "seconds": 300 },
          "targetType": "power",
          "target": {
            "type": "power",
            "value": { "unit": "percent_ftp", "value": 105 }
          },
          "intensity": "active",
          "notes": "Hard effort"
        },
        {
          "stepIndex": 2,
          "durationType": "time",
          "duration": { "type": "time", "seconds": 180 },
          "targetType": "power",
          "target": {
            "type": "power",
            "value": { "unit": "percent_ftp", "value": 50 }
          },
          "intensity": "rest",
          "notes": "Recovery"
        },
        {
          "stepIndex": 3,
          "durationType": "time",
          "duration": { "type": "time", "seconds": 300 },
          "targetType": "power",
          "target": {
            "type": "power",
            "value": { "unit": "percent_ftp", "value": 50 }
          },
          "intensity": "cooldown",
          "notes": "Easy cooldown"
        }
      ]
    }
  }
}

Ready to convert? Follow the Quick Start.