Skip to content

`.pose` file format specification

A .pose file is a single JSON document containing everything a consumer needs to track an movement. This page is the byte-by-byte spec.

The Dart authority is Movement (packages/pose_flow/lib/src/models/unified_exercise.dart). Anything the docs and the model disagree about, trust the model.

Top-level shape

{
"id": "squat",
"version": "1.0.0",
"name": "Squat",
"category": "fitness",
"cameraAngle": "front",
"formatVersion": 2,
"createdAt": "2026-05-27T10:00:00.000Z",
"positions": [],
"phaseConfigs": [...],
"trackingPoints": [...],
"formRules": [...],
"repDetection": {...},
"angleBands": {...},
"repScoring": {...},
"metadata": {...}
}

Required fields

FieldTypeNotes
idstringStable identifier, kebab-case recommended.
versionstringAuthor-supplied semver (NOT the format version).
namestringDisplay name.
positionsarrayReference pose frames for the optional vector-matching rep-counting mode. Empty for .pose files authored in PoseFlow Studio.
repDetectionobjectHow a rep is counted. See below.
createdAtISO 8601 stringUTC timestamp.

Optional fields

FieldTypeDefaultNotes
formatVersioninteger1Set to 2 for .pose files. Consumers branch on this.
categorystring'fitness'Domain: fitness, rehab, mobility, etc.
cameraAngle'front' | 'side' | 'any''front'High-level authoring hint. NOT a runtime lens selector.
phaseConfigsarray[]Phases for the rule-based path. Required when positions is empty.
trackingPointsarray[]Per-frame measurement channels.
formRulesarray[]Form analysis rules.
angleBandsobject{}Per-bucket measurement bands. Keyed by bucket id.
repScoringobjectdisabledAnti-cheat rep quality gate.
metadataobject{}Author-defined. CMS bakes its fields under metadata.cms.*.

phaseConfigs[i]

{
"id": "down",
"name": "Down",
"type": "transition",
"trackingPoints": ["left_knee", "right_knee"],
"duration": { "min": 0, "max": 5000 },
"transitions": [
{
"toPhase": "up",
"conditions": [
{
"trackingPoint": "left_knee",
"operator": "gt",
"value": 160
}
]
}
]
}
FieldTypeNotes
idstringPhase identifier (unique within the movement).
namestringDisplay name.
type'transition' | 'rep' | 'hold'Drives some auto-cycle behaviour.
trackingPointsstring[]IDs of channels relevant to this phase (UI hint).
duration{ min, max } (ms)Hold-time bounds. Max enforces a force-transition when exceeded.
transitionsarrayOutgoing transitions. Each has a toPhase + condition list.

PhaseCondition

{
"trackingPoint": "left_knee",
"operator": "lt",
"value": 120,
"holdTime": 200
}
OperatorComparesvalue shape
gttracking value > thresholdnumber
lttracking value < thresholdnumber
eqtracking value ≈ threshold (±2°)number
betweentracking value ∈ [min, max][number, number]

holdTime (optional, ms): the condition must remain satisfied for at least this many ms before the transition fires. Used to prevent flicker on noisy frames.

trackingPoints[i]

{
"id": "left_knee",
"name": "Left knee angle",
"type": "angle",
"side": "left",
"landmarks": ["left_hip", "left_knee", "left_ankle"],
"config": {
"targetValue": 90,
"tolerance": 20
},
"relativeToBaseline": false
}
FieldTypeNotes
idstringStable identifier for cross-references from formRules + angleBands.
typeTrackingPointTypeOne of angle, distance, proximity, velocity, stability, ratio, position.
side'left' | 'right' | 'both' | 'any'Anatomical side. both evaluates both sides and emits two values.
landmarksstring[]pose landmark names. Length depends on type (angle: 3, distance/proximity: 2, ratio: 4, position: 1).
configobjectType-specific config, see below.
relativeToBaselinebooleanWhen true, emit value - baseline_at_phase_entry instead of raw value.

Per-type config shapes

angle (3 landmarks):

{ "targetValue": 90, "tolerance": 20 }

distance (2 landmarks):

{ "minDistance": 0.0, "maxDistance": 1.0 }

ratio (4 landmarks: numerator distance + denominator distance):

{} // ratio = dist(L0,L1) / dist(L2,L3)

position (1 landmark):

{ "axis": "x" } // or "y"

velocity (1 landmark, derived from a base tracking point):

{ "baseTrackingPointId": "left_wrist_y", "windowMs": 200 }

stability (1 channel, rolling max-min):

{ "baseTrackingPointId": "left_knee", "windowMs": 1000 }

formRules[i]

{
"id": "knee-collapse-down",
"trackingPoint": "left_knee_valgus",
"operator": "lt",
"value": [15],
"appliesToPhase": "down",
"trigger": "onViolation",
"severity": "warning",
"feedback": {
"text": "Push your knees out",
"audioCue": "knee-collapse.mp3"
}
}
FieldTypeNotes
idstringStable rule identifier.
trackingPointstringChannel to evaluate.
operatorConditionOperatorgt, lt, eq, between.
valuenumber[]Single threshold or [min, max] for between.
appliesToPhasestring?Phase id. Null = applies always.
trigger'onViolation' | 'onCompliance' | 'always'When to fire feedback. Defaults to onViolation.
severity'info' | 'warning' | 'error'Drives the host UI’s colour + filter.
feedback{ text, audioCue?, hapticPattern? }What to surface.
retrospectiveobject?When set, the rule fires at phase end against an aggregate (avg/peak/min/max) instead of per frame.

repDetection

{
"strategy": "sequence",
"countOn": "sequenceComplete",
"phases": ["up", "down"],
"phaseAlternatives": [["up", "down", "up"]],
"primaryPhase": "up"
}
FieldTypeNotes
strategy'cycle' | 'sequence' | 'hold' | 'compound'Counting mode.
countOn'phaseEnter' | 'phaseExit' | 'sequenceComplete' | 'duration'When in the lifecycle.
phasesstring[]The cycle order (also used as the fallback sequence).
phaseAlternativesstring[][]Alternative sequences, a rep counts on any one match.
primaryPhasestringFor cycle strategy: the phase entry that ticks.
sequencesPhaseSequenceConfig[]Richer config with per-sequence metadata (mask landmarks, hold durations).

angleBands

{
"front_hip": [
{
"phaseId": "down",
"channelId": "spine_lean",
"min": -8,
"max": 8,
"cameraBucket": "front_hip"
}
],
"90left_hip": [
{
"phaseId": "down",
"channelId": "spine_lean",
"min": -5,
"max": 15,
"cameraBucket": "90left_hip"
}
]
}

One entry per (channel, phase, bucket) triple. The runtime gates view-dependent measurements on the live-detected bucket. See camera buckets.

repScoring

{
"enabled": true,
"minQualityScore": 60,
"weights": {
"formScore": 0.5,
"rangeOfMotion": 0.3,
"tempo": 0.2,
"stability": 0.0
},
"dimensions": {
"formScore": true,
"rangeOfMotion": true,
"tempo": true,
"stability": false
}
}

Optional. When enabled: false, every completed rep counts. When true, a rep only counts if its weighted quality ≥ minQualityScore. Disabling a dimension zero-weights it and renormalises the others.

metadata

Author-defined Map<String, dynamic>. Hosts that need to attach managed fields alongside the tracking definition typically nest them under a host-namespaced key:

"metadata": {
"host": {
"image_url": "...",
"video_url": "...",
"body_part": ["LOWER_BODY"],
"muscle_groups": ["QUADS"],
"modality": ["STRENGTH"],
"difficulty": "BEGINNER",
"category_ids": [...],
"supported_buckets": ["front_hip", "90left_hip"]
},
"authoredBy": "poseflow_studio"
}

Consumers that don’t care about host-specific fields can ignore the whole metadata object, the runtime contract is fully defined by the structural fields above.

Round-trip guarantee

Movement.fromJson(jsonDecode(file)).toJson() is canonical JSON-stable (keys in deterministic order, no null fields written). You can diff two .pose files byte-for-byte to detect authoring drift.