Edit a bout's touches
const url = 'http://localhost:8000/api/bouts/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/touches/';const options = { method: 'PUT', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"ours_side":"left","touches":[{"id":"3f2a9c1d0b7e","period":0,"window_s":[6.8,14.8],"include":true},{"id":"8c1e4a0d2f9b","period":0,"window_s":[40.1,47.1],"include":false},{"period":0,"onset_s":94,"double":false}],"touches_version":"5be1c0a92f3d7e64"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request PUT \ --url http://localhost:8000/api/bouts/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/touches/ \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "ours_side": "left", "touches": [ { "id": "3f2a9c1d0b7e", "period": 0, "window_s": [ 6.8, 14.8 ], "include": true }, { "id": "8c1e4a0d2f9b", "period": 0, "window_s": [ 40.1, 47.1 ], "include": false }, { "period": 0, "onset_s": 94, "double": false } ], "touches_version": "5be1c0a92f3d7e64" }'The coach’s edit of the whole touch list: drop or restore a touch (include), move or stretch its window, tick a double, set our fencer’s side from a touch onward (ours_declared), or add a touch (no id). A touch that has a session is frozen. The response marks with check_sides every touch where the fencers may have changed ends — Analyse is refused until each is answered with ours_declared. A new touch may send only onset_s; its window is then cut by the weapon. The edit names the touches_version it was made on; 409 when the list has moved since (a period read landed, another tab edited it) — reload and edit again.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Request Body
Section titled “Request Body”object
left- Left side of the stripright- Right side of the strip
One touch of a list edit: as a touch reads, except that a new one may send only its moment.
object
[start, end] in the period’s video. A new touch may send only onset_s; the server then cuts the weapon’s window around it.
The bout’s touches_version the edit was made on. Required with touches: an edit of a list that has moved since is refused with 409.
Examples
Drop a touch, widen another, add one by its moment
{ "ours_side": "left", "touches": [ { "id": "3f2a9c1d0b7e", "period": 0, "window_s": [ 6.8, 14.8 ], "include": true }, { "id": "8c1e4a0d2f9b", "period": 0, "window_s": [ 40.1, 47.1 ], "include": false }, { "period": 0, "onset_s": 94, "double": false } ], "touches_version": "5be1c0a92f3d7e64"}object
left- Left side of the stripright- Right side of the strip
One touch of a list edit: as a touch reads, except that a new one may send only its moment.
object
[start, end] in the period’s video. A new touch may send only onset_s; the server then cuts the weapon’s window around it.
The bout’s touches_version the edit was made on. Required with touches: an edit of a list that has moved since is refused with 409.
object
left- Left side of the stripright- Right side of the strip
One touch of a list edit: as a touch reads, except that a new one may send only its moment.
object
[start, end] in the period’s video. A new touch may send only onset_s; the server then cuts the weapon’s window around it.
The bout’s touches_version the edit was made on. Required with touches: an edit of a list that has moved since is refused with 409.
Responses
Section titled “Responses”object
object
object
Send it back with a touch edit; a list that has moved since answers 409.
object
The touch’s pair metrics and our fencer’s footwork and hand, number or category text by key (bout_distance_at_attack_m, bout_first_to_close, bout_strip_position_m, bout_first_to_extend, bout_extension_lead_ms, step_count, advance_count, retreat_count, step_rate, weapon_hand_peak_speed).
object
object
Touches whose analysis came back with metrics.
object
Null until a touch reads it.
object
Null until a touch reads it.
object
Advances over the analysed touches, before each attack; null with none read.
Retreats over the analysed touches; null with none read.
Steps per minute, mean over touches.
object
Foil when a white lamp was read on any touch (only foil has an off-target lamp); null when the lamps cannot tell. Never overrides the declared weapon.
Sessions of quota the bout’s one Analyse debits.
The longest period video the lamp read takes, seconds; the uploader checks it before uploading.
Examples
A bout
{ "id": "6d0f3c2a-1b4e-4c8a-9f21-7e5d3a9b0c11", "client": null, "name": "Pool vs Smith", "weapon": "foil", "handedness": "right", "opponent_handedness": "", "ours_side": "left", "periods": [ { "id": "a3e1c9f0-52b7-4d16-8c0e-91f4b2d7e6a5", "index": 0, "video_url": "https://media.example.com/teams/9b1c7e2a/sessions/2026/10/0c5b1d7e-6a2f-4e3b-8d19-5f0a7c2e4b61.mp4", "duration_s": 312.4, "calibration": { "lamps": [ [ 0.44, 0.49, 0.47, 0.52 ], [ 0.51, 0.49, 0.54, 0.52 ] ] }, "detect_status": "done", "detect_error": "" } ], "touches": [ { "id": "3f2a9c1d0b7e", "period": 0, "onset_s": 13.8, "lamps": [ "green", "red" ], "window_s": [ 7.8, 14.8 ], "double": true, "source": "lamps", "include": true, "ours_declared": null, "ours": "left", "check_sides": false, "session_id": null } ], "touches_version": "5be1c0a92f3d7e64", "touch_sessions": [], "quota_units": 3, "max_period_s": 480, "aggregates": { "touches": 1, "analysed": 0, "doubles": 1, "opponent_handedness": null, "lamps": { "red": 1, "green": 1, "white": 0 }, "distance_at_attack_mean_m": null, "first_to_close": null, "first_to_extend": null, "extension_lead_mean_ms": null, "steps_per_touch_mean": null, "advances": null, "retreats": null, "step_rate_mean": null, "weapon_hand_peak_speed_mean_m_s": null, "weapon_hand_peak_speed_max_m_s": null, "strip_positions": [], "weapon_suggestion": null, "weapon_mismatch": false }, "analysed_at": null, "created_at": "2026-10-26T10:00:00Z"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Example generated
{ "detail": "example"}