A technique video is a clip a trainee records of themselves doing an exercise. The coach reviews it and sends feedback as a text note, a voice note, or both. This module is the coach side: list the queue, open one video, and save the review. The trainee app submits videos and marks feedback as seen through its own routes in modules/trainee/trainee.routes.ts (POST /technique-videos and /technique-videos/:id/feedback-seen under the trainee lane). Those routes are not documented on this page. Tenancy is enforced through the trainee relation: every query filters on client: { studioId }. The module does not apply coach permission scoping from middleware/coach-access.ts.

The TechniqueVideo row

All endpoints return full Prisma rows with no field selection.

Endpoints

GET /v1/web/technique-videos

Lists the studio’s technique videos, newest first. Auth: web lane, any role.
string
One of PENDING, IN_REVIEW, REVIEWED. Omit to return every status.
string
Limit the list to one trainee.
integer
default:"1"
Page number, positive.
integer
default:"20"
Rows per page. Positive, maximum 500.
Rows are ordered by createdAt descending. The findMany and the count run in parallel with the same filter. Response:
Errors: UNAUTHORIZED when the request has no studio context. VALIDATION (422) when the query is invalid, for example an unknown status.

POST /v1/web/technique-videos

Creates a technique video row for a trainee. Returns 201. Auth: web lane, any role.
string
required
The trainee. Must belong to the caller’s studio.
string
The exercise the clip shows. The service does not check that the id exists.
string
required
Video location. Minimum length 1. Not validated as a URL.
string
Poster image location.
The service checks the trainee with repo.clientInStudio, then inserts the row. The status starts at PENDING from the column default. No activity record, notification or job is created. Response: the created TechniqueVideo row.
Errors: NOT_FOUND (client not found) when clientId is not in the studio. VALIDATION for a bad body. UNAUTHORIZED with no studio context.

GET /v1/web/technique-videos/:id

Returns one technique video. Auth: web lane, any role.
string
required
The technique video id.
Response: one TechniqueVideo row, same shape as the create response. Errors: NOT_FOUND (technique video not found) when the id does not exist or belongs to another studio.

PATCH /v1/web/technique-videos/:id

Saves a review: status, written note, voice note and reviewer fields. Every body field is optional and only the fields sent are written. Auth: web lane, any role.
string
required
The technique video id.
string
One of PENDING, IN_REVIEW, REVIEWED.
string
Written feedback for the trainee.
string
Location of a recorded voice note.
string
Id of the reviewer. The caller supplies it. The service does not derive it from req.auth.
date
When the review happened. Parsed with z.coerce.date(). The caller supplies it. The service does not stamp it.
The service loads the video first so a foreign id fails with NOT_FOUND, then updates the row. Side effect: when coachNote is present and not blank after trimming, the service calls repo.recordFeedbackActivity. That inserts a ClientActivity row so the feedback shows in the trainee’s activity feed:
entityName is the note cut to 120 characters. A failure to write the activity is swallowed (.catch(() => null)), so the review still saves. A voice note on its own does not create an activity row. This module sends no push notification. Response: the updated TechniqueVideo row.
Errors: NOT_FOUND (technique video not found), VALIDATION for a bad body, UNAUTHORIZED with no studio context.