FormAssignment. The trainee app lists the pending ones, renders the form from its schema and submits the answers.
Mount: /v1/trainee/forms, router traineeFormsRouter in src/modules/forms/forms.routes.ts. The service is built by createWiredFormsService in forms.wiring.ts, which attaches every submit side effect.
Lane: trainee. The router mounts authenticateTrainee only. It does not use the app access guard. Instead the forms service applies its own rule: while Client.planFrozenOn is set, the list is empty and the other two endpoints return 403. Preview tokens can read forms and cannot submit. See Authentication.
Documents to sign (form type PDF_SIGNATURE) are never served here. They are signed on a public web page. See Public endpoints.
Endpoints
GET /v1/trainee/forms
Lists the forms waiting for the trainee.
Auth: trainee token.
No parameters.
An assignment is listed when all of these hold:
- it belongs to the trainee and the token’s studio,
- its status is
PENDING, - it has no
dueAt, ordueAtis now or earlier, - its template is not a document to sign.
string
The assignment id. This is the
:id used by the two endpoints below, not the template id.string | null
A short note the coach attached when sending.
string
The form’s display name for trainees (
schema.settings.displayName) when set, else the coach-facing name.string
INTAKE, CHECK_IN or ONE_TIME.GET /v1/trainee/forms/:id
Returns one assignment with the full form schema to render.
Auth: trainee token.
string
required
The assignment id.
normalizeFormSchema, which upgrades older stored shapes to version 2. Completed assignments can also be loaded. Check status before offering a submit.
Response: 200.
Schema reference
FormSchemaV2 is defined in src/modules/forms/form-schema.ts.
Field types (
FORM_FIELD_TYPES):
settings holds rtl, pages (each with id, optional title, ctaLabel and its own condition), and optional submitLabel, successMessage, displayName and thankYou.
Conditions
A field or a page can be shown only when another field’s answer matches. Operators by source kind (form-conditions.ts):
The engine fails open: a condition that is disabled, points at a missing field or cannot be evaluated leaves the field visible. The same engine is mirrored in the web builder and the mobile app, so all three agree on what is visible. The server evaluates it again on submit and never trusts the client’s view.
Errors:
POST /v1/trainee/forms/:id
Submits the trainee’s answers and completes the assignment.
Auth: trainee token. Refused for preview tokens.
string
required
The assignment id.
object
required
A map of field
key to answer. Unknown keys and answers to hidden fields are dropped.
A required field with an empty answer (
undefined, null, empty string or empty array) is an error. Fields hidden by a condition are skipped entirely, so a required but hidden field never blocks the submit.
What the service does on a valid submit:
- Strips answers of hidden and display-only fields (
stripHiddenAnswers). - Creates a
FormResponsewith the answers, the assignment’sformVersion, aschemaSnapshotof the schema as answered, andsubmittedAt. - Marks the assignment
COMPLETEDwithcompletedAt. - For an
INTAKEform, tells the subscription starter that the intake form is done. A subscription that was waiting for it starts now. - Copies
saveToClientanswers onto the trainee, best effort (applyClientCardSaves):email,phone,israeli_id,age,height,birth_date(which also recomputes age),goalandweightupdate theClientprofile.- Each
weightanswer also creates aWeightEntrywithsource: "form"and the response id. body_measurementanswers are merged into today’sDailyMetric.measurementsunder the field’sconfig.measurementkey (chest,waist,hips,armorthigh).image_uploadanswers that look like image URLs create aClientPhotowithsource: "form", the response id and the field’sconfig.pose. A failure in this step never fails the submission.
- Fires the submitted hook (
formSubmittedHandler), also best effort:FORM_FILLEDwebhooks registered through the Automation API are queued for delivery.- A
FORM_FILLEDautomation flow runs when the studio has one. Otherwise the legacy “form filled” task is created for the coach. - The same pair for low ratings: the rating-below flow, or the legacy rating task.
FormResponse row.
schemaSnapshot is the full schema and is shortened here. The row may carry further columns of the FormResponse model.
Errors:
Answer errors are not Zod errors. The message is the first error in Hebrew followed by the field label in quotes, clipped to 60 characters, and
details lists every failed field:
details[].key to outline the fields that need attention.
Notifications for pending forms
The trainee lane has no endpoint for “new form” events. Announcements are pushed:- A coach-assigned form pushes the
FORM_SENTnotification right away when the trainee has a device registered. notifyPendingFormsannounces forms that became visible later (adueAtthat has now passed) and runs again whenever the app registers a push token. SeePOST /v1/trainee/push-tokenon Profile and home.
GET /v1/trainee/forms when it opens and when it receives a form notification.