← Beat Offset / API
Tokens

Drive the rhythm coach from your own code

The measuring happens in the browser: the page plays the click, records each tap and matches it to the beat grid. The coach over HTTP takes the result of that - a timing profile - and returns a diagnosis and a practice set. A natural use is your own practice app, a drum-teacher dashboard or a MIDI tool that already has tap times: compute the same profile (the page's engine.js does it, and runs in node) and send it.

The page never trusts the model's numbers. Every statistic a reply cites is looked up in the browser's own measurements and marked verified, mismatched or not checkable, and every exercise must name a pattern from the library in a meter it exists in. Over the API nothing has checked the reply yet - see the end of the examples for the same checks in node.

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"beat-offset"}). Send it as Authorization: Bearer …. The run body is the input object: never wrap it as {"input": {…}} - that returns 200, warns unknown field 'input', and hides every field from the model.

StatusCodeMeaning
400validation_errorThe body is not a JSON object.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the coach is metered and needs a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

taskWhenWhat comes back
diagnoseAfter a take.2-5 findings each citing one statistic, 0-4 callouts pinned to bars, a practice set of 3-5 exercises (warm-up, skill, performance, cool-down), a next step. progress is empty.
recheckAfter practising that set: a new take, plus previous (the earlier reply, the diagnosed take's numbers and what was practised).The same shape, plus progress: each statistic before and after with a verdict.

The declared input schema is scalar-only, so every object or list travels as a JSON string. task is required.

FieldTypeMeaning
taskstringdiagnose or recheck
pattern_id, pattern_namestringA pattern from the library (library lists them)
meterstring3/4, 4/4, 5/4 or 6/8 (the 6/8 beat is the dotted quarter)
level, tempo, bars, latency_msnumberPattern level 1-5, BPM of the beat, bars recorded, latency already subtracted
click_modestringfull, dropout (2 on, 2 off), backbeat, countin
subdiv, audiobooleanSubdivision clicks; whether the click was audible (false = visual only)
statsJSON stringSession numbers: hit_rate, pocket_pct, mean_ms, spread_ms, drift_ms_per_bar, felt_bpm, clicked/silent, ...
positions, bar_statsJSON stringPer position (beat1..beat7, e, and, a, trip, let) and per bar
taps, missed, clip_noteJSON string / stringThe clipped tap list (first 24 in order + 12 worst), up to 16 missed notes, and what was left out
libraryJSON stringThe patterns the coach may prescribe: {id, name, level, meters}
learner_notestringOptional goal or instrument
previousJSON stringrecheck only
retry_notestringSet by the page when it retries an unreadable reply once

The reply

One JSON object: {"task", "headline", "findings":[{"finding","stat","position","bar","value","severity"}], "callouts":[{"bar","text"}], "prescription":[{"phase","title","pattern","meter","tempo","bars","click","minutes","cue","why"}], "progress":[{"stat","position","before","after","verdict"}], "next_step"}. stat is a session statistic (pocket_pct, mean_ms, ...), position_mean_ms/position_spread_ms with a position, or bar_mean_ms/bar_spread_ms with a bar.

Worked examples

The three examples on the page, with their real run inputs and the saved replies the page replays.

diagnose: straight eighths at 96 with the click dropping out, rushing the &

Input:

{
 "task": "diagnose",
 "pattern_id": "eighths",
 "pattern_name": "Straight eighths",
 "meter": "4/4",
 "level": 1,
 "tempo": 96,
 "bars": 8,
 "click_mode": "dropout",
 "subdiv": false,
 "audio": true,
 "latency_ms": 0,
 "stats": "(a JSON string - decoded below)",
 "positions": "(a JSON string - decoded below)",
 "bar_stats": "(a JSON string - decoded below)",
 "taps": "(a JSON string - decoded below)",
 "missed": "(a JSON string - decoded below)",
 "clip_note": "measured 62 taps, sent 36 (the first 24 in order and the 12 furthest off the grid). Every aggregate covers every tap.",
 "library": "(a JSON string - decoded below)",
 "learner_note": "I play drums in a covers band; the singer says I speed up in the verses."
}

stats, decoded:

{
 "expected": 64,
 "hits": 62,
 "missed": 2,
 "extra": 1,
 "hit_rate": 96.9,
 "pocket_pct": 64.1,
 "mean_ms": -16.1,
 "spread_ms": 17.3,
 "median_ms": -14,
 "mean_abs_ms": 18.8,
 "p90_abs_ms": 39.7,
 "early": 21,
 "late": 0,
 "drift_ms_per_bar": -1.3,
 "drift_r2": 0.03,
 "felt_bpm": 96.1,
 "first_half_ms": -15.7,
 "second_half_ms": -16.5,
 "clicked": {
  "n": 31,
  "mean_ms": -10.8,
  "spread_ms": 16.7
 },
 "silent": {
  "n": 31,
  "mean_ms": -21.4,
  "spread_ms": 16.6
 },
 "tendency": "rushing"
}

positions, decoded:

[
 {
  "key": "beat1",
  "label": "1",
  "expected": 8,
  "n": 8,
  "mean_ms": -2.2,
  "spread_ms": 10.2
 },
 {
  "key": "and",
  "label": "&",
  "expected": 32,
  "n": 31,
  "mean_ms": -29.6,
  "spread_ms": 12.2
 },
 {
  "key": "beat2",
  "label": "2",
  "expected": 8,
  "n": 7,
  "mean_ms": -1.1,
  "spread_ms": 9
 },
 {
  "key": "beat3",
  "label": "3",
  "expected": 8,
  "n": 8,
  "mean_ms": -5.9,
  "spread_ms": 8.9
 },
 {
  "key": "beat4",
  "label": "4",
  "expected": 8,
  "n": 8,
  "mean_ms": -0.9,
  "spread_ms": 9.7
 }
]

bar_stats, decoded:

[
 {
  "bar": 1,
  "clicked": true,
  "expected": 8,
  "n": 8,
  "mean_ms": -15.8,
  "spread_ms": 15.6
 },
 {
  "bar": 2,
  "clicked": true,
  "expected": 8,
  "n": 7,
  "mean_ms": -8.3,
  "spread_ms": 18.2
 },
 {
  "bar": 3,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -13.1,
  "spread_ms": 17.3
 },
 {
  "bar": 4,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -24.6,
  "spread_ms": 15.8
 },
 {
  "bar": 5,
  "clicked": true,
  "expected": 8,
  "n": 8,
  "mean_ms": -10.4,
  "spread_ms": 21.1
 },
 {
  "bar": 6,
  "clicked": true,
  "expected": 8,
  "n": 8,
  "mean_ms": -8.2,
  "spread_ms": 13.2
 },
 {
  "bar": 7,
  "clicked": false,
  "expected": 8,
  "n": 7,
  "mean_ms": -22,
  "spread_ms": 16
 },
 {
  "bar": 8,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -26,
  "spread_ms": 17.2
 }
]

taps, decoded:

[
 {
  "n": 1,
  "bar": 1,
  "pos": "beat1",
  "off": -6.2,
  "why": "first"
 },
 {
  "n": 2,
  "bar": 1,
  "pos": "and",
  "off": -31.6,
  "why": "first"
 },
 {
  "n": 3,
  "bar": 1,
  "pos": "beat2",
  "off": -1,
  "why": "first"
 },
 {
  "n": 4,
  "bar": 1,
  "pos": "and",
  "off": -43.9,
  "why": "first"
 },
 {
  "n": 5,
  "bar": 1,
  "pos": "beat3",
  "off": -4.5,
  "why": "first"
 },
 {
  "n": 6,
  "bar": 1,
  "pos": "and",
  "off": -18,
  "why": "first"
 },
 {
  "n": 7,
  "bar": 1,
  "pos": "beat4",
  "off": -1.7,
  "why": "first"
 },
 {
  "n": 8,
  "bar": 1,
  "pos": "and",
  "off": -19.8,
  "why": "first"
 },
 {
  "n": 9,
  "bar": 2,
  "pos": "beat1",
  "off": 1.3,
  "why": "first"
 },
 {
  "n": 10,
  "bar": 2,
  "pos": "and",
  "off": -32.8,
  "why": "first"
 },
 {
  "n": 11,
  "bar": 2,
  "pos": "beat2",
  "off": 10.5,
  "why": "first"
 },
 {
  "n": 12,
  "bar": 2,
  "pos": "and",
  "off": -32.1,
  "why": "first"
 },
 {
  "n": 13,
  "bar": 2,
  "pos": "beat3",
  "off": 10.6,
  "why": "first"
 },
 {
  "n": 15,
  "bar": 2,
  "pos": "beat4",
  "off": -5.8,
  "why": "first"
 },
 {
  "n": 16,
  "bar": 2,
  "pos": "and",
  "off": -10,
  "why": "first"
 },
 {
  "n": 17,
  "bar": 3,
  "pos": "beat1",
  "off": 3.9,
  "why": "first"
 },
 {
  "n": 18,
  "bar": 3,
  "pos": "and",
  "off": -22.5,
  "why": "first"
 },
 {
  "n": 19,
  "bar": 3,
  "pos": "beat2",
  "off": -7,
  "why": "first"
 },
 {
  "n": 20,
  "bar": 3,
  "pos": "and",
  "off": -39.7,
  "why": "first"
 },
 {
  "n": 21,
  "bar": 3,
  "pos": "beat3",
  "off": 0.2,
  "why": "first"
 },
 {
  "n": 22,
  "bar": 3,
  "pos": "and",
  "off": -28.8,
  "why": "first"
 },
 {
  "n": 23,
  "bar": 3,
  "pos": "beat4",
  "off": 9.3,
  "why": "first"
 },
 {
  "n": 24,
  "bar": 3,
  "pos": "and",
  "off": -19.9,
  "why": "first"
 },
 {
  "n": 25,
  "bar": 4,
  "pos": "beat1",
  "off": -17.5,
  "why": "first"
 },
 {
  "n": 64,
  "bar": 8,
  "pos": "and",
  "off": -53.5,
  "why": "worst"
 },
 {
  "n": 26,
  "bar": 4,
  "pos": "and",
  "off": -49.6,
  "why": "worst"
 },
 {
  "n": 56,
  "bar": 7,
  "pos": "and",
  "off": -41.8,
  "why": "worst"
 },
 {
  "n": 52,
  "bar": 7,
  "pos": "and",
  "off": -40.5,
  "why": "worst"
 },
 {
  "n": 60,
  "bar": 8,
  "pos": "and",
  "off": -39.7,
  "why": "worst"
 },
 {
  "n": 58,
  "bar": 8,
  "pos": "and",
  "off": -37.5,
  "why": "worst"
 },
 {
  "n": 32,
  "bar": 4,
  "pos": "and",
  "off": -37.3,
  "why": "worst"
 },
 {
  "n": 34,
  "bar": 5,
  "pos": "and",
  "off": -37.1,
  "why": "worst"
 },
 {
  "n": 38,
  "bar": 5,
  "pos": "and",
  "off": -36.5,
  "why": "worst"
 }
... (shortened here)

missed, decoded:

[
 {
  "n": 14,
  "bar": 2,
  "pos": "and"
 },
 {
  "n": 51,
  "bar": 7,
  "pos": "beat2"
 }
]

library, decoded:

[
 {
  "id": "pulse",
  "name": "Quarter pulse",
  "level": 1,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "eighths",
  "name": "Straight eighths",
  "level": 1,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "backbeat",
  "name": "Backbeat 2 and 4",
  "level": 1,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "offbeats",
  "name": "Off-beats (the &)",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "triplets",
  "name": "Eighth triplets",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "swing",
  "name": "Swung eighths",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "charleston",
  "name": "Charleston",
  "level": 2,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "sixteenths",
  "name": "Straight sixteenths",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "gallop",
  "name": "Gallop (8th + two 16ths)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "reverse-gallop",
  "name": "Reverse gallop (two 16ths + 8th)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "tresillo",
  "name": "Tresillo 3-3-2",
  "level": 3,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "skip-triplet",
  "name": "Triplet skip (trip-let)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "e-and-a",
  "name": "Sixteenths without the beat",
  "level": 4,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "the-a",
  "name": "Only the 'a'",
  "level": 4,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "hemiola",
  "name": "Hemiola (two over three)",
  "level": 4,
  "meters": [
   "3/4"
  ]
 },
 {
  "id": "poly-3-2",
  "name": "Three over two",
  "level": 5,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "jig-pulse",
  "name": "6/8 pulse",
  "level": 1,
  "meters": [
   "6/8"
  ]
 },
 {
  "id": "jig-eighths",
  "name": "6/8 eighths",
  "level": 2,
  "meters": [
   "6/8"
  ]
 },
 {
  "id": "jig-bell",
  "name": "6/8 bell",
  "level": 4,
  "meters": [
   "6/8"
  ]
 }
]

Reply:

{
 "task": "diagnose",
 "headline": "Your beats sit right on the click, but every \"and\" comes early, and when the click drops out the whole groove slides ahead - that is the speeding-up your singer hears.",
 "findings": [
  {
   "finding": "The \"and\" between the beats is rushed: it lands about 30 ms early on average, while beats 1-4 sit within a few ms of the click.",
   "stat": "position_mean_ms",
   "position": "and",
   "bar": 0,
   "value": -29.6,
   "severity": "high"
  },
  {
   "finding": "When the click goes silent you push further ahead: silent bars average -21.4 ms against -10.8 ms in clicked bars.",
   "stat": "silent_mean_ms",
   "position": "",
   "bar": 0,
   "value": -21.4,
   "severity": "high"
  },
  {
   "finding": "Overall the take leans early - 21 notes early and none late - so only 64.1% of notes are in the pocket.",
   "stat": "pocket_pct",
   "position": "",
   "bar": 0,
   "value": 64.1,
   "severity": "medium"
  },
  {
   "finding": "The last silent bar is the furthest ahead of the take.",
   "stat": "bar_mean_ms",
   "position": "",
   "bar": 8,
   "value": -26,
   "severity": "medium"
  },
  {
   "finding": "Two notes were missed (an \"and\" in bar 2 and beat 2 in bar 7), and one extra tap crept in.",
   "stat": "missed",
   "position": "",
   "bar": 0,
   "value": 2,
   "severity": "low"
  }
 ],
 "callouts": [
  {
   "bar": 4,
   "text": "Click off: taps slide early, averaging -24.6 ms."
  },
  {
   "bar": 7,
   "text": "Click off again: early slide returns and beat 2 is missed."
  },
  {
   "bar": 8,
   "text": "Furthest ahead of the take: -26 ms."
  }
 ],
 "prescription": [
  {
   "phase": "warmup",
   "title": "Settle the pulse",
   "pattern": "pulse",
   "meter": "4/4",
   "tempo": 76,
   "bars": 8,
   "click": "full",
   "minutes": 3,
   "cue": "Let each beat land, breathe out on the click.",
   "why": "Starts from what already works - your on-beats are close to the click."
  },
  {
   "phase": "skill",
   "title": "Wait for the &",
   "pattern": "offbeats",
   "meter": "4/4",
   "tempo": 76,
   "bars": 8,
   "click": "full",
   "minutes": 5,
   "cue": "Count one-AND out loud and hit only on the AND, late in your mind.",
   "why": "Isolates the rushed \"and\" (-29.6 ms) at a tempo where it can sit in the middle."
  },
  {
   "phase": "skill",
   "title": "Eighths, slow and even",
   "pattern": "eighths",
   "meter": "4/4",
   "tempo": 82,
   "bars": 8,
   "click": "full",
   "minutes": 4,
   "cue": "Make the gap after the beat as long as the gap before it.",
   "why": "Puts the \"and\" back between the beats so the eighths are even."
  },
  {
   "phase": "performance",
   "title": "Eighths with the click dropping out",
   "pattern": "eighths",
   "meter": "4/4",
   "tempo": 96,
   "bars": 16,
   "click": "dropout",
   "minutes": 5,
   "cue": "When the click stops, keep counting it in your head - don't fill the space.",
   "why": "Answers the early slide in silent bars (-21.4 ms) - the verse with no click to lean on."
  },
  {
   "phase": "cooldown",
   "title": "Easy pulse",
   "pattern": "pulse",
   "meter": "4/4",
   "tempo": 76,
   "bars": 8,
   "click": "backbeat",
   "minutes": 2,
   "cue": "Lazy and heavy, sit back on every beat.",
   "why": "Ends on a relaxed, laid-back feel to counter the overall rush."
  }
 ],
 "progress": [],
 "next_step": "Re-take Straight eighths at 96 with the dropout click and see whether the \"and\" and the silent bars have moved toward zero."
}

diagnose: a clean take of sixteenths at 72

Input:

{
 "task": "diagnose",
 "pattern_id": "sixteenths",
 "pattern_name": "Straight sixteenths",
 "meter": "4/4",
 "level": 3,
 "tempo": 72,
 "bars": 8,
 "click_mode": "full",
 "subdiv": false,
 "audio": true,
 "latency_ms": 0,
 "stats": "(a JSON string - decoded below)",
 "positions": "(a JSON string - decoded below)",
 "bar_stats": "(a JSON string - decoded below)",
 "taps": "(a JSON string - decoded below)",
 "missed": "[]",
 "clip_note": "measured 128 taps, sent 36 (the first 24 in order and the 12 furthest off the grid). Every aggregate covers every tap.",
 "library": "(a JSON string - decoded below)",
 "learner_note": ""
}

stats, decoded:

{
 "expected": 128,
 "hits": 128,
 "missed": 0,
 "extra": 0,
 "hit_rate": 100,
 "pocket_pct": 96.9,
 "mean_ms": 6.9,
 "spread_ms": 9.1,
 "median_ms": 7.2,
 "mean_abs_ms": 9.5,
 "p90_abs_ms": 18,
 "early": 0,
 "late": 4,
 "drift_ms_per_bar": -0.6,
 "drift_r2": 0.02,
 "felt_bpm": 72,
 "first_half_ms": 8,
 "second_half_ms": 5.9,
 "clicked": {
  "n": 128,
  "mean_ms": 6.9,
  "spread_ms": 9.1
 },
 "silent": {
  "n": 0,
  "mean_ms": null,
  "spread_ms": null
 },
 "tendency": "centred"
}

positions, decoded:

[
 {
  "key": "beat1",
  "label": "1",
  "expected": 8,
  "n": 8,
  "mean_ms": 7.3,
  "spread_ms": 12.5
 },
 {
  "key": "e",
  "label": "e",
  "expected": 32,
  "n": 32,
  "mean_ms": 6.9,
  "spread_ms": 8.9
 },
 {
  "key": "and",
  "label": "&",
  "expected": 32,
  "n": 32,
  "mean_ms": 7.6,
  "spread_ms": 9.8
 },
 {
  "key": "a",
  "label": "a",
  "expected": 32,
  "n": 32,
  "mean_ms": 6.6,
  "spread_ms": 8.9
 },
 {
  "key": "beat2",
  "label": "2",
  "expected": 8,
  "n": 8,
  "mean_ms": 3.5,
  "spread_ms": 9.5
 },
 {
  "key": "beat3",
  "label": "3",
  "expected": 8,
  "n": 8,
  "mean_ms": 11.8,
  "spread_ms": 5.7
 },
 {
  "key": "beat4",
  "label": "4",
  "expected": 8,
  "n": 8,
  "mean_ms": 4.1,
  "spread_ms": 6.8
 }
]

bar_stats, decoded:

[
 {
  "bar": 1,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 8.7,
  "spread_ms": 9.4
 },
 {
  "bar": 2,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 7.7,
  "spread_ms": 9
 },
 {
  "bar": 3,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 7.1,
  "spread_ms": 8.9
 },
 {
  "bar": 4,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 8.7,
  "spread_ms": 9.8
 },
 {
  "bar": 5,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 5.8,
  "spread_ms": 9.6
 },
 {
  "bar": 6,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 10.1,
  "spread_ms": 6.7
 },
 {
  "bar": 7,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 4.3,
  "spread_ms": 8.1
 },
 {
  "bar": 8,
  "clicked": true,
  "expected": 16,
  "n": 16,
  "mean_ms": 3.3,
  "spread_ms": 10.7
 }
]

taps, decoded:

[
 {
  "n": 1,
  "bar": 1,
  "pos": "beat1",
  "off": 25.9,
  "why": "first"
 },
 {
  "n": 2,
  "bar": 1,
  "pos": "e",
  "off": 2.7,
  "why": "first"
 },
 {
  "n": 3,
  "bar": 1,
  "pos": "and",
  "off": 9.3,
  "why": "first"
 },
 {
  "n": 4,
  "bar": 1,
  "pos": "a",
  "off": 17.1,
  "why": "first"
 },
 {
  "n": 5,
  "bar": 1,
  "pos": "beat2",
  "off": 12.1,
  "why": "first"
 },
 {
  "n": 6,
  "bar": 1,
  "pos": "e",
  "off": 8.1,
  "why": "first"
 },
 {
  "n": 7,
  "bar": 1,
  "pos": "and",
  "off": 28.1,
  "why": "first"
 },
 {
  "n": 8,
  "bar": 1,
  "pos": "a",
  "off": 0.1,
  "why": "first"
 },
 {
  "n": 9,
  "bar": 1,
  "pos": "beat3",
  "off": 13.3,
  "why": "first"
 },
 {
  "n": 10,
  "bar": 1,
  "pos": "e",
  "off": -1,
  "why": "first"
 },
 {
  "n": 11,
  "bar": 1,
  "pos": "and",
  "off": 6.4,
  "why": "first"
 },
 {
  "n": 12,
  "bar": 1,
  "pos": "a",
  "off": 6.7,
  "why": "first"
 },
 {
  "n": 13,
  "bar": 1,
  "pos": "beat4",
  "off": 9.8,
  "why": "first"
 },
 {
  "n": 14,
  "bar": 1,
  "pos": "e",
  "off": 1.8,
  "why": "first"
 },
 {
  "n": 15,
  "bar": 1,
  "pos": "and",
  "off": -8.1,
  "why": "first"
 },
 {
  "n": 16,
  "bar": 1,
  "pos": "a",
  "off": 6.3,
  "why": "first"
 },
 {
  "n": 17,
  "bar": 2,
  "pos": "beat1",
  "off": 10,
  "why": "first"
 },
 {
  "n": 18,
  "bar": 2,
  "pos": "e",
  "off": 9.5,
  "why": "first"
 },
 {
  "n": 19,
  "bar": 2,
  "pos": "and",
  "off": 7.7,
  "why": "first"
 },
 {
  "n": 20,
  "bar": 2,
  "pos": "a",
  "off": -9.2,
  "why": "first"
 },
 {
  "n": 21,
  "bar": 2,
  "pos": "beat2",
  "off": 10.5,
  "why": "first"
 },
 {
  "n": 22,
  "bar": 2,
  "pos": "e",
  "off": 2.1,
  "why": "first"
 },
 {
  "n": 23,
  "bar": 2,
  "pos": "and",
  "off": 18.6,
  "why": "first"
 },
 {
  "n": 24,
  "bar": 2,
  "pos": "a",
  "off": -8,
  "why": "first"
 },
 {
  "n": 67,
  "bar": 5,
  "pos": "and",
  "off": 27.5,
  "why": "worst"
 },
 {
  "n": 46,
  "bar": 3,
  "pos": "e",
  "off": 26.5,
  "why": "worst"
 },
 {
  "n": 55,
  "bar": 4,
  "pos": "and",
  "off": 23.5,
  "why": "worst"
 },
 {
  "n": 57,
  "bar": 4,
  "pos": "beat3",
  "off": 23.4,
  "why": "worst"
 },
 {
  "n": 87,
  "bar": 6,
  "pos": "and",
  "off": 21.8,
  "why": "worst"
 },
 {
  "n": 30,
  "bar": 2,
  "pos": "e",
  "off": 20.8,
  "why": "worst"
 },
 {
  "n": 60,
  "bar": 4,
  "pos": "a",
  "off": 20.6,
  "why": "worst"
 },
 {
  "n": 114,
  "bar": 8,
  "pos": "e",
  "off": 20.5,
  "why": "worst"
 },
 {
  "n": 108,
  "bar": 7,
  "pos": "a",
  "off": 20.1,
  "why": "worst"
 },
 {
  "n": 76,
  "bar": 5,
  "pos": "a",
  "off": 18,
  "why": "worst"
... (shortened here)

library, decoded:

[
 {
  "id": "pulse",
  "name": "Quarter pulse",
  "level": 1,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "eighths",
  "name": "Straight eighths",
  "level": 1,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "backbeat",
  "name": "Backbeat 2 and 4",
  "level": 1,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "offbeats",
  "name": "Off-beats (the &)",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "triplets",
  "name": "Eighth triplets",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "swing",
  "name": "Swung eighths",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "charleston",
  "name": "Charleston",
  "level": 2,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "sixteenths",
  "name": "Straight sixteenths",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "gallop",
  "name": "Gallop (8th + two 16ths)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "reverse-gallop",
  "name": "Reverse gallop (two 16ths + 8th)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "tresillo",
  "name": "Tresillo 3-3-2",
  "level": 3,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "skip-triplet",
  "name": "Triplet skip (trip-let)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "e-and-a",
  "name": "Sixteenths without the beat",
  "level": 4,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "the-a",
  "name": "Only the 'a'",
  "level": 4,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "hemiola",
  "name": "Hemiola (two over three)",
  "level": 4,
  "meters": [
   "3/4"
  ]
 },
 {
  "id": "poly-3-2",
  "name": "Three over two",
  "level": 5,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "jig-pulse",
  "name": "6/8 pulse",
  "level": 1,
  "meters": [
   "6/8"
  ]
 },
 {
  "id": "jig-eighths",
  "name": "6/8 eighths",
  "level": 2,
  "meters": [
   "6/8"
  ]
 },
 {
  "id": "jig-bell",
  "name": "6/8 bell",
  "level": 4,
  "meters": [
   "6/8"
  ]
 }
]

Reply:

{
 "task": "diagnose",
 "headline": "This is a clean, steady take - every note hit and 96.9% in the pocket - so it is time to move on, not to fix anything.",
 "findings": [
  {
   "finding": "Every one of the sixteenths was played, with nothing missed and no extra taps.",
   "stat": "hit_rate",
   "position": "",
   "bar": 0,
   "value": 100,
   "severity": "low"
  },
  {
   "finding": "Nearly all notes sit inside the pocket.",
   "stat": "pocket_pct",
   "position": "",
   "bar": 0,
   "value": 96.9,
   "severity": "low"
  },
  {
   "finding": "The spread is tight - your notes are consistent with each other.",
   "stat": "spread_ms",
   "position": "",
   "bar": 0,
   "value": 9.1,
   "severity": "low"
  },
  {
   "finding": "You sit a touch behind the click on average - a relaxed feel, not a problem.",
   "stat": "mean_ms",
   "position": "",
   "bar": 0,
   "value": 6.9,
   "severity": "low"
  },
  {
   "finding": "No drift: the tempo you kept matches the click.",
   "stat": "felt_bpm",
   "position": "",
   "bar": 0,
   "value": 72,
   "severity": "low"
  }
 ],
 "callouts": [],
 "prescription": [
  {
   "phase": "warmup",
   "title": "Sixteenths, home tempo",
   "pattern": "sixteenths",
   "meter": "4/4",
   "tempo": 66,
   "bars": 8,
   "click": "full",
   "minutes": 3,
   "cue": "Small, even strokes, same height every note.",
   "why": "Warms up the pattern you already play cleanly."
  },
  {
   "phase": "skill",
   "title": "Gallop",
   "pattern": "gallop",
   "meter": "4/4",
   "tempo": 66,
   "bars": 8,
   "click": "full",
   "minutes": 4,
   "cue": "Say one-and-a, and keep the gap where the e was.",
   "why": "Progression: a level-3 pattern that removes notes from the grid you now own."
  },
  {
   "phase": "skill",
   "title": "Sixteenths without the beat",
   "pattern": "e-and-a",
   "meter": "4/4",
   "tempo": 60,
   "bars": 8,
   "click": "full",
   "minutes": 5,
   "cue": "Feel the beat in your foot and play only e-and-a.",
   "why": "Progression: a level-4 step that tests whether your even sixteenths hold without the downbeat."
  },
  {
   "phase": "performance",
   "title": "Sixteenths, click drops out",
   "pattern": "sixteenths",
   "meter": "4/4",
   "tempo": 72,
   "bars": 16,
   "click": "dropout",
   "minutes": 5,
   "cue": "Keep the click alive in your head through the silent bars.",
   "why": "This take had the full click throughout; the next test is holding the 72 BPM you kept without it."
  }
 ],
 "progress": [],
 "next_step": "Once the dropout take stays this clean, raise the tempo a few BPM or take Sixteenths without the beat up to 72."
}

recheck: the eighths again after practising the prescribed set

Input:

{
 "task": "recheck",
 "pattern_id": "eighths",
 "pattern_name": "Straight eighths",
 "meter": "4/4",
 "level": 1,
 "tempo": 96,
 "bars": 8,
 "click_mode": "dropout",
 "subdiv": false,
 "audio": true,
 "latency_ms": 0,
 "stats": "(a JSON string - decoded below)",
 "positions": "(a JSON string - decoded below)",
 "bar_stats": "(a JSON string - decoded below)",
 "taps": "(a JSON string - decoded below)",
 "missed": "(a JSON string - decoded below)",
 "clip_note": "measured 63 taps, sent 36 (the first 24 in order and the 12 furthest off the grid). Every aggregate covers every tap.",
 "library": "(a JSON string - decoded below)",
 "learner_note": "",
 "previous": "(a JSON string - decoded below)"
}

stats, decoded:

{
 "expected": 64,
 "hits": 63,
 "missed": 1,
 "extra": 0,
 "hit_rate": 98.4,
 "pocket_pct": 95.3,
 "mean_ms": -5.5,
 "spread_ms": 11.2,
 "median_ms": -6.7,
 "mean_abs_ms": 10.2,
 "p90_abs_ms": 18.5,
 "early": 1,
 "late": 1,
 "drift_ms_per_bar": -0.1,
 "drift_r2": 0,
 "felt_bpm": 96,
 "first_half_ms": -6.6,
 "second_half_ms": -4.3,
 "clicked": {
  "n": 31,
  "mean_ms": -3.4,
  "spread_ms": 11.7
 },
 "silent": {
  "n": 32,
  "mean_ms": -7.5,
  "spread_ms": 10.5
 },
 "tendency": "centred"
}

positions, decoded:

[
 {
  "key": "beat1",
  "label": "1",
  "expected": 8,
  "n": 8,
  "mean_ms": 5.9,
  "spread_ms": 9.2
 },
 {
  "key": "and",
  "label": "&",
  "expected": 32,
  "n": 31,
  "mean_ms": -12.3,
  "spread_ms": 8.7
 },
 {
  "key": "beat2",
  "label": "2",
  "expected": 8,
  "n": 8,
  "mean_ms": 2.4,
  "spread_ms": 13
 },
 {
  "key": "beat3",
  "label": "3",
  "expected": 8,
  "n": 8,
  "mean_ms": -4.7,
  "spread_ms": 5.4
 },
 {
  "key": "beat4",
  "label": "4",
  "expected": 8,
  "n": 8,
  "mean_ms": 1.1,
  "spread_ms": 6.6
 }
]

bar_stats, decoded:

[
 {
  "bar": 1,
  "clicked": true,
  "expected": 8,
  "n": 8,
  "mean_ms": -2.2,
  "spread_ms": 8
 },
 {
  "bar": 2,
  "clicked": true,
  "expected": 8,
  "n": 8,
  "mean_ms": -8,
  "spread_ms": 7.9
 },
 {
  "bar": 3,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -10.5,
  "spread_ms": 12.2
 },
 {
  "bar": 4,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -5.6,
  "spread_ms": 12.2
 },
 {
  "bar": 5,
  "clicked": true,
  "expected": 8,
  "n": 8,
  "mean_ms": -1.9,
  "spread_ms": 12.4
 },
 {
  "bar": 6,
  "clicked": true,
  "expected": 8,
  "n": 7,
  "mean_ms": -1.1,
  "spread_ms": 17.9
 },
 {
  "bar": 7,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -7.9,
  "spread_ms": 11.2
 },
 {
  "bar": 8,
  "clicked": false,
  "expected": 8,
  "n": 8,
  "mean_ms": -5.9,
  "spread_ms": 7.2
 }
]

taps, decoded:

[
 {
  "n": 1,
  "bar": 1,
  "pos": "beat1",
  "off": 12.2,
  "why": "first"
 },
 {
  "n": 2,
  "bar": 1,
  "pos": "and",
  "off": -10.4,
  "why": "first"
 },
 {
  "n": 3,
  "bar": 1,
  "pos": "beat2",
  "off": -9.2,
  "why": "first"
 },
 {
  "n": 4,
  "bar": 1,
  "pos": "and",
  "off": 5.4,
  "why": "first"
 },
 {
  "n": 5,
  "bar": 1,
  "pos": "beat3",
  "off": -4.6,
  "why": "first"
 },
 {
  "n": 6,
  "bar": 1,
  "pos": "and",
  "off": -6.7,
  "why": "first"
 },
 {
  "n": 7,
  "bar": 1,
  "pos": "beat4",
  "off": 2.5,
  "why": "first"
 },
 {
  "n": 8,
  "bar": 1,
  "pos": "and",
  "off": -6.7,
  "why": "first"
 },
 {
  "n": 9,
  "bar": 2,
  "pos": "beat1",
  "off": 3.3,
  "why": "first"
 },
 {
  "n": 10,
  "bar": 2,
  "pos": "and",
  "off": -8.9,
  "why": "first"
 },
 {
  "n": 11,
  "bar": 2,
  "pos": "beat2",
  "off": -13.6,
  "why": "first"
 },
 {
  "n": 12,
  "bar": 2,
  "pos": "and",
  "off": -17.3,
  "why": "first"
 },
 {
  "n": 13,
  "bar": 2,
  "pos": "beat3",
  "off": 3.3,
  "why": "first"
 },
 {
  "n": 14,
  "bar": 2,
  "pos": "and",
  "off": -15.2,
  "why": "first"
 },
 {
  "n": 15,
  "bar": 2,
  "pos": "beat4",
  "off": -5.6,
  "why": "first"
 },
 {
  "n": 16,
  "bar": 2,
  "pos": "and",
  "off": -10.2,
  "why": "first"
 },
 {
  "n": 17,
  "bar": 3,
  "pos": "beat1",
  "off": -11.3,
  "why": "first"
 },
 {
  "n": 18,
  "bar": 3,
  "pos": "and",
  "off": 1.5,
  "why": "first"
 },
 {
  "n": 19,
  "bar": 3,
  "pos": "beat2",
  "off": 2.3,
  "why": "first"
 },
 {
  "n": 20,
  "bar": 3,
  "pos": "and",
  "off": -24.5,
  "why": "first"
 },
 {
  "n": 21,
  "bar": 3,
  "pos": "beat3",
  "off": -2.8,
  "why": "first"
 },
 {
  "n": 22,
  "bar": 3,
  "pos": "and",
  "off": -10,
  "why": "first"
 },
 {
  "n": 23,
  "bar": 3,
  "pos": "beat4",
  "off": -6.8,
  "why": "first"
 },
 {
  "n": 24,
  "bar": 3,
  "pos": "and",
  "off": -32.1,
  "why": "first"
 },
 {
  "n": 43,
  "bar": 6,
  "pos": "beat2",
  "off": 26.2,
  "why": "worst"
 },
 {
  "n": 46,
  "bar": 6,
  "pos": "and",
  "off": -24.5,
  "why": "worst"
 },
 {
  "n": 28,
  "bar": 4,
  "pos": "and",
  "off": -19.9,
  "why": "worst"
 },
 {
  "n": 54,
  "bar": 7,
  "pos": "and",
  "off": -19.7,
  "why": "worst"
 },
 {
  "n": 44,
  "bar": 6,
  "pos": "and",
  "off": -18.5,
  "why": "worst"
 },
 {
  "n": 56,
  "bar": 7,
  "pos": "and",
  "off": -18.2,
  "why": "worst"
 },
 {
  "n": 64,
  "bar": 8,
  "pos": "and",
  "off": -18.1,
  "why": "worst"
 },
 {
  "n": 52,
  "bar": 7,
  "pos": "and",
  "off": -17.8,
  "why": "worst"
 },
 {
  "n": 26,
  "bar": 4,
  "pos": "and",
  "off": -17.7,
  "why": "worst"
 }
... (shortened here)

missed, decoded:

[
 {
  "n": 42,
  "bar": 6,
  "pos": "and"
 }
]

library, decoded:

[
 {
  "id": "pulse",
  "name": "Quarter pulse",
  "level": 1,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "eighths",
  "name": "Straight eighths",
  "level": 1,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "backbeat",
  "name": "Backbeat 2 and 4",
  "level": 1,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "offbeats",
  "name": "Off-beats (the &)",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "triplets",
  "name": "Eighth triplets",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "swing",
  "name": "Swung eighths",
  "level": 2,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "charleston",
  "name": "Charleston",
  "level": 2,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "sixteenths",
  "name": "Straight sixteenths",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "gallop",
  "name": "Gallop (8th + two 16ths)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "reverse-gallop",
  "name": "Reverse gallop (two 16ths + 8th)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "tresillo",
  "name": "Tresillo 3-3-2",
  "level": 3,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "skip-triplet",
  "name": "Triplet skip (trip-let)",
  "level": 3,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "e-and-a",
  "name": "Sixteenths without the beat",
  "level": 4,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "the-a",
  "name": "Only the 'a'",
  "level": 4,
  "meters": [
   "3/4",
   "4/4",
   "5/4"
  ]
 },
 {
  "id": "hemiola",
  "name": "Hemiola (two over three)",
  "level": 4,
  "meters": [
   "3/4"
  ]
 },
 {
  "id": "poly-3-2",
  "name": "Three over two",
  "level": 5,
  "meters": [
   "4/4"
  ]
 },
 {
  "id": "jig-pulse",
  "name": "6/8 pulse",
  "level": 1,
  "meters": [
   "6/8"
  ]
 },
 {
  "id": "jig-eighths",
  "name": "6/8 eighths",
  "level": 2,
  "meters": [
   "6/8"
  ]
 },
 {
  "id": "jig-bell",
  "name": "6/8 bell",
  "level": 4,
  "meters": [
   "6/8"
  ]
 }
]

previous, decoded:

{
 "headline": "Your beats sit right on the click, but every \"and\" comes early, and when the click drops out the whole groove slides ahead - that is the speeding-up your singer hears.",
 "findings": [
  {
   "finding": "The \"and\" between the beats is rushed: it lands about 30 ms early on average, while beats 1-4 sit within a few ms of the click.",
   "stat": "position_mean_ms",
   "position": "and",
   "bar": 0,
   "value": -29.6
  },
  {
   "finding": "When the click goes silent you push further ahead: silent bars average -21.4 ms against -10.8 ms in clicked bars.",
   "stat": "silent_mean_ms",
   "position": "",
   "bar": 0,
   "value": -21.4
  },
  {
   "finding": "Overall the take leans early - 21 notes early and none late - so only 64.1% of notes are in the pocket.",
   "stat": "pocket_pct",
   "position": "",
   "bar": 0,
   "value": 64.1
  },
  {
   "finding": "The last silent bar is the furthest ahead of the take.",
   "stat": "bar_mean_ms",
   "position": "",
   "bar": 8,
   "value": -26
  },
  {
   "finding": "Two notes were missed (an \"and\" in bar 2 and beat 2 in bar 7), and one extra tap crept in.",
   "stat": "missed",
   "position": "",
   "bar": 0,
   "value": 2
  }
 ],
 "prescription": [
  {
   "id": "rx1",
   "title": "Settle the pulse",
   "pattern": "pulse",
   "meter": "4/4",
   "tempo": 76,
   "click": "full"
  },
  {
   "id": "rx2",
   "title": "Wait for the &",
   "pattern": "offbeats",
   "meter": "4/4",
   "tempo": 76,
   "click": "full"
  },
  {
   "id": "rx3",
   "title": "Eighths, slow and even",
   "pattern": "eighths",
   "meter": "4/4",
   "tempo": 82,
   "click": "full"
  },
  {
   "id": "rx4",
   "title": "Eighths with the click dropping out",
   "pattern": "eighths",
   "meter": "4/4",
   "tempo": 96,
   "click": "dropout"
  },
  {
   "id": "rx5",
   "title": "Easy pulse",
   "pattern": "pulse",
   "meter": "4/4",
   "tempo": 76,
   "click": "backbeat"
  }
 ],
 "before": {
  "pattern_id": "eighths",
  "meter": "4/4",
  "tempo": 96,
  "stats": {
   "expected": 64,
   "hits": 62,
   "missed": 2,
   "extra": 1,
   "hit_rate": 96.9,
   "pocket_pct": 64.1,
   "mean_ms": -16.1,
   "spread_ms": 17.3,
   "median_ms": -14,
   "mean_abs_ms": 18.8,
   "p90_abs_ms": 39.7,
   "early": 21,
   "late": 0,
   "drift_ms_per_bar": -1.3,
   "drift_r2": 0.03,
   "felt_bpm": 96.1,
   "first_half_ms": -15.7,
   "second_half_ms": -16.5,
   "clicked": {
    "n": 31,
    "mean_ms": -10.8,
    "spread_ms": 16.7
   },
   "silent": {
    "n": 31,
    "mean_ms": -21.4,
    "spread_ms": 16.6
   },
   "tendency": "rushing"
  },
  "positions": [
 
... (shortened here)

Reply:

{
 "task": "recheck",
 "headline": "Big step forward - you practised all five exercises, the silent bars no longer run away and 95.3% of notes are now in the pocket; the \"and\" is still a little early.",
 "findings": [
  {
   "finding": "The \"and\" still lands ahead of the beats, though far less than before.",
   "stat": "position_mean_ms",
   "position": "and",
   "bar": 0,
   "value": -12.3,
   "severity": "medium"
  },
  {
   "finding": "The silent bars now stay close to the clicked ones - the slide when the click drops out is mostly gone.",
   "stat": "silent_mean_ms",
   "position": "",
   "bar": 0,
   "value": -7.5,
   "severity": "low"
  },
  {
   "finding": "Almost every note is in the pocket now.",
   "stat": "pocket_pct",
   "position": "",
   "bar": 0,
   "value": 95.3,
   "severity": "low"
  },
  {
   "finding": "One note was missed - an \"and\" in bar 6 - and no extra taps.",
   "stat": "missed",
   "position": "",
   "bar": 0,
   "value": 1,
   "severity": "low"
  }
 ],
 "callouts": [
  {
   "bar": 6,
   "text": "One \"and\" missed here."
  }
 ],
 "prescription": [
  {
   "phase": "warmup",
   "title": "Eighths, easy",
   "pattern": "eighths",
   "meter": "4/4",
   "tempo": 82,
   "bars": 8,
   "click": "full",
   "minutes": 3,
   "cue": "Make the gap after the beat as long as the gap before it.",
   "why": "Re-settles the even eighths that improved most."
  },
  {
   "phase": "skill",
   "title": "Wait for the & again",
   "pattern": "offbeats",
   "meter": "4/4",
   "tempo": 80,
   "bars": 8,
   "click": "full",
   "minutes": 5,
   "cue": "Count one-AND out loud and let the AND arrive, don't reach for it.",
   "why": "The \"and\" is still early (-12.3 ms); this drill had the lowest best pocket of the set."
  },
  {
   "phase": "performance",
   "title": "Eighths, backbeat click only",
   "pattern": "eighths",
   "meter": "4/4",
   "tempo": 96,
   "bars": 16,
   "click": "backbeat",
   "minutes": 5,
   "cue": "Feel 1 and 3 yourself; the click only answers on 2 and 4.",
   "why": "Keeps the silent-bar gain while giving you less click to lean on, like a real verse."
  }
 ],
 "progress": [
  {
   "stat": "position_mean_ms",
   "position": "and",
   "before": -29.6,
   "after": -12.3,
   "verdict": "better"
  },
  {
   "stat": "silent_mean_ms",
   "position": "",
   "before": -21.4,
   "after": -7.5,
   "verdict": "better"
  },
  {
   "stat": "pocket_pct",
   "position": "",
   "before": 64.1,
   "after": 95.3,
   "verdict": "better"
  },
  {
   "stat": "mean_ms",
   "position": "",
   "before": -16.1,
   "after": -5.5,
   "verdict": "better"
  },
  {
   "stat": "spread_ms",
   "position": "",
   "before": 17.3,
   "after": 11.2,
   "verdict": "better"
  },
  {
   "stat": "missed",
   "position": "",
   "before": 2,
   "after": 1,
   "verdict": "better"
  }
 ],
 "next_step": "Re-take Straight eighths at 96 with the dropout click and check that the \"and\" moves closer to the beats."
}

The page re-checks every reply; over the API, so should you

The page's own code is plain scripts you can load in node after global.window = {}, in this order: patterns.js (the library, notation and click plan), engine.js (Engine.session turns tap times into the full profile; Engine.clipTaps is the clipping), coach.js (Coach.buildInput builds these exact run bodies; Coach.normalize and Coach.reconcile check a reply against the take: every cited number within tolerance, every exercise a real pattern in a real meter, every callout on a bar that exists).

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://beat-offset.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"beat-offset"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model reads gpt-5.6-terra and model_alias gpt-terra.

# input.json: the run input object - {"task": "diagnose", "pattern_id": "eighths", "meter": "4/4", "tempo": 96, "stats": "...", ...}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the task and the input: a retry with the same key never bills twice. The page uses beat-offset:<task>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="beat-offset:diagnose:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: beat-offset:diagnose:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json