/v1/automation, router automationApiRouter in src/modules/automation-api/automation-api.routes.ts. Webhook handlers come from src/modules/automation-hooks/.
Lane: automation. Every route runs requirePartnerKey, the same studio API key check as the Partner API. A key resolves to one studio and has no scopes. Keys are created by an OWNER or HEAD_COACH through POST /v1/web/studios/current/api-keys, documented on the partner page.
/v1 limiter only, 120 requests per 60 seconds per IP by default. The module adds no limiter of its own.
Response envelope
This lane does not use the standard{ ok, data } envelope. It has its own, defined in automation-api.envelope.ts, because automation tools show the response message to a coach when a step fails.
Success is always HTTP 200, including creates:
automationErrorMiddleware, which maps errors like this:
A bad or missing key is also answered in this shape: 401 with
missing partner api key or invalid partner api key.
Input handling
Automation tools serialize everything as strings and send empty strings for fields left blank. The schemas inautomation-api.schema.ts coerce accordingly:
No field on this lane uses
null to clear a value. Leaving a field blank on an update leaves it unchanged.
Lists use offset pagination: limit (default 50, max 200) and offset (default 0). Paginated responses carry items, total, limit, offset and hasMore.
Notifications
Writes on this lane can reach real trainees, so each endpoint says what it sends:- Creating a trainee sends nothing unless
sendWhatsappInviteistrue. AnonboardingFormIddoes send that form with its normal push. - Assigning a plan or a program pushes a notification by default. Send
notify: falsefor bulk imports. - Assigning a form has three states, described on that endpoint.
Auditing
Writes record anAuditLog row with the API key id as the actor. Actions: automation.trainee.create, automation.trainee.delete, automation.subscription.assign, automation.program.assign, automation.task.create, automation.hook.register and automation.hook.remove.
Connection
GET /v1/automation/validate
The connection test. Make calls it when a coach adds the connection.
Auth: studio API key.
Response: 200.
studio not found.
Trainees
A trainee object on this lane has these fields (traineeSelect in the repository, shaped by present):
externalId is the id the caller supplied at creation, or null. Internally it is stored on Client.externalUserId as automation:<studioId>:<externalId>, and the internal column is never returned.
POST /v1/automation/trainees
Creates a trainee, optionally with a subscription, coaches and forms.
Auth: studio API key.
string
required
Non-empty.
string
required
Non-empty.
string
required
Any common format. Must normalize to at least 9 characters.
string
A valid email.
string
A stable id from the source system. This is the idempotency key. See below.
string
A date.
string
MALE, FEMALE or OTHER.number
Positive.
number
Positive.
string
Free text.
number
Non-negative integer.
number
Non-negative integer.
string | string[]
A comma-separated string or an array. At most 50 tags.
string[]
At most 20 coach ids, from
GET /v1/automation/rpc/coaches.string
A plan id. Also creates the subscription.
string
Subscription start date.
number
Non-negative integer, in agorot. Overrides the plan’s price for this trainee.
number
Positive integer. How long the plan runs for this trainee. Required when the plan does not fix a duration.
string
DAYS or MONTHS.string
An intake form to send right after creation. A document to sign is refused here.
string
The recurring check-in form for this trainee.
string
WEEKLY, BIWEEKLY or MONTHLY.boolean
default:"false"
Send the WhatsApp app invite to the trainee.
string
default:"error"
error, return_existing or update. What to do when a trainee with this phone already exists.externalIdmatch. If a trainee with thisexternalIdalready exists in the studio, it is returned withcreated: false. Nothing is updated. This makes a retried bundle or a re-run scenario safe.- Phone match. Otherwise, if a trainee with the same phone exists,
onDuplicatePhonedecides:errorreturns 409,return_existingreturns the trainee, andupdateapplies the profile fields from the body (names, email, birth date, gender, height, weight, goal, goals, tags, coaches) and returns the updated trainee. Subscription fields are not applied to an existing trainee. - Create. Otherwise the trainee is created through the same
clients.createthe coach web app uses, with the API key id as the actor. Plan limits of the studio’s billing plan apply.
onboardingFormId is given and that trainee has no pending assignment of it, the form is sent. A failure there is logged and does not fail the call.
Response: 200.
trainee is the full trainee object, shortened here. When an existing trainee is returned, created is false and the message is Trainee already existed, returned the existing one.
Errors:
GET /v1/automation/trainees/search
Searches trainees.
Auth: studio API key.
string
A name or a phone. A value made only of digits, spaces,
+, - and brackets is treated as a phone and matched on its variants. Anything else is a case-insensitive name match.string
ACTIVE, PAUSED, CHURN_RISK, CHURNED or ARCHIVED.string
Only trainees assigned to this coach.
string
Only trainees with this exact tag.
string
A date. Created at or after it.
string
A date. Updated at or after it. Useful for polling.
number
default:"50"
1 to 200.
number
default:"0"
Non-negative.
GET /v1/automation/trainees/by-phone/:phone
Finds one trainee by phone.
Auth: studio API key.
string
required
At least 3 characters. Any common format.
{ "found": false, "trainee": null } with the message No trainee with that phone.
Errors: 400 when the value does not normalize to a phone of at least 6 characters.
GET /v1/automation/trainees/:id
Returns one trainee with their live subscriptions.
Auth: studio API key.
string
required
The trainee id.
data: { trainee, subscriptions }. subscriptions is the list from ClientSubscriptionsService.listLive, the same rows as GET /v1/automation/trainees/:id/subscriptions.
Errors: 400 when no trainee with that id exists in the studio.
PATCH /v1/automation/trainees/:id
Updates profile fields.
Auth: studio API key.
string
required
The trainee id.
firstName, lastName, phone, email, birthDate, gender, heightCm, weightKg, goal, stepsGoal, waterGoalMl, tags, coachIds, updateFormId, updateCadence. Types and limits are the same as on create. tags and coachIds replace the stored lists when sent. Blank fields are ignored.
Subscription and plan fields cannot be changed here. Subscriptions are the source of truth for a trainee’s plan and status. Use the subscription endpoints.
Response: 200 with data: { trainee } and the message Trainee updated.
Errors: 400 for an unknown trainee or invalid values. Errors from the shared clients service pass through with their own status.
DELETE /v1/automation/trainees/:id
Deletes a trainee and their history. This cannot be undone.
Auth: studio API key.
string
required
The trainee id.
boolean
default:"false"
Must be
true to delete.confirm: true nothing is deleted and the call still returns 200, with an explanation in message. That is deliberate: a scenario wired without reading the warning gets a clear sentence, not a validation error.
Response: 200.
data is { "deleted": false } and the message starts with Nothing was deleted.
Errors: 400 for an unknown trainee.
Subscriptions
GET /v1/automation/products
Lists the studio’s active plans.
Auth: studio API key.
string
Case-insensitive match on the plan name.
number
default:"50"
1 to 200.
number
default:"0"
Non-negative.
null priceAgorot or durationValue means the plan does not fix that value and it is decided per trainee.
Errors: 400 for invalid query values.
GET /v1/automation/trainees/:id/subscriptions
Lists the trainee’s live subscriptions.
Auth: studio API key.
string
required
The trainee id.
data: { items }. Each item is a ClientSubscription row as returned by repo.liveByClient. The exact column list is defined in the client-subscriptions module and is not restated here.
Errors: 400 for an unknown trainee.
POST /v1/automation/trainees/:id/subscription
Assigns or renews a plan.
Auth: studio API key.
string
required
The trainee id.
string
required
The plan id.
string
Start date.
number
Non-negative integer. Overrides the plan’s price.
number
Positive integer. Required when the plan does not fix a duration.
string
DAYS or MONTHS.boolean
default:"true"
false uses a silent notifier so the trainee gets no push.ClientSubscriptionsService.addSubscription, the same code path as the coach web app, including the subscription-assigned task hook.
Response: 200 with data: { subscription } and the message Plan assigned.
Errors: 400 for an unknown trainee or plan, or a plan with no duration and none given. The message names the plan.
POST /v1/automation/trainees/:id/subscription/freeze
Freezes the trainee’s running plan.
Auth: studio API key.
string
required
The trainee id.
data: { subscriptions }, the live subscriptions after the change, and the message Plan frozen.
Errors: 400, for example plan is already frozen or client has no active plan.
POST /v1/automation/trainees/:id/subscription/reactivate
Reactivates a frozen plan.
Auth: studio API key.
string
required
The trainee id.
data: { subscriptions } and the message Plan reactivated.
Errors: 400, for example plan is not frozen.
POST /v1/automation/trainees/:id/subscription/cancel
Cancels one subscription.
Auth: studio API key.
string
required
The trainee id.
string
required
The subscription to cancel, from the subscriptions list.
data: { subscriptions } and the message Plan canceled.
Errors: 400 for an unknown trainee or a missing subscriptionId. Refusals from the subscription service pass through.
Programs
GET /v1/automation/program-templates
Lists the studio’s program templates.
Auth: studio API key.
Query: search, limit, offset, as on the products list.
Response: 200 with paginated items of { id, name, type }. type is TRAINING or NUTRITION.
Errors: 400 for invalid query values.
GET /v1/automation/trainees/:id/programs
Lists the trainee’s programs, newest first, at most 50.
Auth: studio API key.
string
required
The trainee id.
POST /v1/automation/trainees/:id/programs/assign
Assigns a training or nutrition program from a template.
Auth: studio API key.
string
required
The trainee id.
string
required
The program template id.
string
Start date.
boolean
default:"true"
false suppresses the push to the trainee.boolean
default:"false"
Allow assigning the same template again while a program from it is active.
replaceExisting is true, the call is refused when the trainee already has an ACTIVE program whose sourceTemplateId is this template. That keeps a re-run scenario from stacking duplicates. The assignment itself goes through ProgramTemplatesService.assign, and a first plan starts a subscription that was waiting for one.
Response: 200 with data: { program } and the message Program assigned.
Errors:
Forms
GET /v1/automation/forms
Lists the studio’s forms that can be sent: active and published.
Auth: studio API key.
Query: search, limit, offset.
Response: 200 with paginated items of { id, name, type }. type is INTAKE, CHECK_IN, ONE_TIME or PDF_SIGNATURE.
Errors: 400 for invalid query values.
POST /v1/automation/trainees/:id/forms/assign
Sends a form to a trainee, or creates a signing link for a document to sign.
Auth: studio API key.
string
required
The trainee id.
string
required
The form id.
string
When the form becomes visible to the trainee.
boolean
Three states.
true: push the notification right away. false: never announce this form. Blank or omitted: no instant push, and the hourly pending-form notifier announces it once under the studio’s settings, when the trainee can open it.string
A note shown with the form. At most 500 characters.
object | array | string
Documents to sign only. Values for the fields the coach fills at send time, keyed by field key. Accepted as an object, as an array of
{ key, value }, or as a JSON string of either. Numbers and booleans are converted to strings. Ignored for other form types.PDF_SIGNATURE form Perform sends nothing: the response carries signingUrl, and the caller delivers that link to the trainee. An inactive form is refused.
Response: 200.
assignment is the FormAssignment row without its token, prefill and frozen schema, shortened here. For a regular form signingUrl is null and the message is Form sent to the trainee.
Errors: 400 for an unknown trainee or form, an inactive form (form template is not active), or invalid fieldValues.
GET /v1/automation/trainees/:id/form-responses
Lists the trainee’s form responses, newest first.
Auth: studio API key.
string
required
The trainee id.
limit, offset. search is accepted and ignored.
Response: 200.
total or hasMore. Page until a short page comes back. Answers are not included. Use a FORM_FILLED webhook to receive them.
Errors: 400 for an unknown trainee.
Weights and messages
GET /v1/automation/trainees/:id/weights
Lists the trainee’s weigh-ins, newest first.
Auth: studio API key.
string
required
The trainee id.
limit, offset.
Response: 200 with data: { items }, each { id, weightKg, recordedAt, source }. No total or hasMore.
Errors: 400 for an unknown trainee.
POST /v1/automation/trainees/:id/weights
Logs a weigh-in.
Auth: studio API key.
string
required
The trainee id.
number
required
Greater than 0, at most 500.
string
A date. Defaults to now.
WeightEntry with source: "automation" and sets Client.weightKg to this value in the same transaction, whatever the recordedAt.
Response: 200.
POST /v1/automation/trainees/:id/message
Sends a push message to the trainee’s app, as the studio.
Auth: studio API key.
string
required
The trainee id.
string
required
1 to 1000 characters. The token
[שם] is replaced with the trainee’s first name.clients.sendPushMessage as a TRAINER_MESSAGE notification, so the studio’s settings for that type apply. When a device accepts the message, a MESSAGE entry is added to the trainee’s timeline. A trainee with no registered device receives nothing and the call still succeeds.
Response: 200 with the message Message sent and the send result from the clients service in data. Its exact fields are defined there and are not restated here.
Errors: 400 for an unknown trainee or an empty message.
Tasks
Tasks areInboxItem rows on the coach work board.
GET /v1/automation/tasks
Lists tasks, newest first.
Auth: studio API key.
string
OPEN, SNOOZED, DONE or DISMISSED.string
Only tasks about this trainee.
string
Only tasks for this coach.
number
default:"50"
1 to 200.
number
default:"0"
Non-negative.
POST /v1/automation/tasks
Creates a task for a coach.
Auth: studio API key.
string
required
Non-empty.
string
The trainee the task is about. Must exist in the studio.
string
At most 2000 characters.
string
A date.
number
default:"1"
Integer 0 to 5.
string
The coach to assign.
type MANUAL and source automation.
Response: 200 with data: { task } in the list item shape and the message Task created.
Errors: 400 for a missing title or an unknown trainee.
PATCH /v1/automation/tasks/:id
Updates a task’s status, snooze time or priority.
Auth: studio API key.
string
required
The task id.
string
OPEN, SNOOZED, DONE or DISMISSED.string
A date.
number
Integer 0 to 5.
data: { task } and the message Task updated.
Errors: 400 when no task with that id exists in the studio.
Webhooks
An automation tool registers a webhook when a scenario with an instant trigger is switched on, and deletes it when the scenario is switched off. Hooks belong to the API key that created them. One event exists today:FORM_FILLED. It fires for every form submission in the studio, from the trainee app and from the public signing page.
POST /v1/automation/hooks
Registers a webhook.
Auth: studio API key.
string
required
The URL to deliver to. Trimmed, at most 2048 characters. Must be
https on a public host. localhost, loopback, private ranges, link-local addresses and .local or .internal hosts are refused.string
default:"FORM_FILLED"
The only value is
FORM_FILLED.string
Narrow the hook to one form. Blank means every form.
secret, 48 hex characters, used to verify deliveries. It is returned only here.
Response: 200.
targetUrl must be an https URL on a public host), an unknown form (form not found), or when the key already has 50 webhooks.
GET /v1/automation/hooks
Lists the webhooks registered by this API key.
Auth: studio API key.
string
FORM_FILLED.data: { hooks }. Each hook has the fields above without secret. lastDeliveryAt and lastStatus show the most recent delivery attempt.
Errors: 400 for an unknown event.
DELETE /v1/automation/hooks/:id
Removes a webhook.
Auth: studio API key.
string
required
The hook id. Only hooks created by this key can be removed.
webhook not found.
Delivery
Deliveries are queued on the BullMQ queueAUTOMATION_HOOK_DELIVER and sent by hook-delivery-worker.ts, off the request path. A trainee’s submission never waits on a webhook.
Each delivery is a POST with these headers:
Verify by computing the same HMAC over the exact bytes received:
- The request times out after 10 seconds.
- A 2xx response is a success.
- A 4xx response other than 429 is final. The delivery is dropped.
- Anything else (5xx, 429, a timeout or a network error) is retried, up to 5 attempts in total with exponential backoff starting at 10 seconds.
- Every attempt updates the hook’s
lastDeliveryAtandlastStatus. Failures increment an internal counter. The code does not deactivate a hook after repeated failures. - A hook whose studio is archived, or that has been deleted, receives nothing.
FORM_FILLED payload
Built by buildFormFilledPayload in automation-hooks.dispatcher.ts:
string
Lower case
form_filled in the body. The header carries FORM_FILLED.number
The ordinal of this submission among all of the trainee’s submitted forms in the studio, of any form type. Computed at send time, not stored.
string | null
The signed PDF for a document to sign.
null for every other form.string | null
The id the caller supplied when creating the trainee through this lane.
array
One entry per answerable field in the order the trainee saw them, with labels, so a scenario can map a field without a second call.
value is null for an unanswered field.Dropdown sources
These feed dynamic dropdowns in Make modules. Each returns a plain array indata, with no pagination. The first five return { id, value } items, which map onto Make’s label and value pair as item.value and item.id.
GET /v1/automation/rpc/products
Lists the studio’s active plans for a dropdown.
Auth: studio API key.
No parameters. Ordered by name.
Response: 200. Items are { id, value }. value is the plan name followed by its price in whole shekels, or by a Hebrew “price per trainee” note for a plan with no fixed price.
Errors: none beyond auth.
GET /v1/automation/rpc/coaches
Lists the studio’s active coaches for a dropdown.
Auth: studio API key.
No parameters. Ordered by name.
Response: 200. Items are { id, value }. value is the coach name.
Errors: none beyond auth.
GET /v1/automation/rpc/forms
Lists the forms that can be sent: active and published.
Auth: studio API key.
No parameters. Ordered by name.
Response: 200. Items are { id, value }. value is the form name with its type in brackets, for example Weekly check-in (CHECK_IN).
Errors: none beyond auth.
GET /v1/automation/rpc/program-templates
Lists every program template of the studio.
Auth: studio API key.
No parameters. Ordered by name.
Response: 200. Items are { id, value }. value is the template name with its type in brackets.
Errors: none beyond auth.
GET /v1/automation/rpc/tags
Lists the tags in use on the studio’s trainees.
Auth: studio API key.
No parameters. Tags are collected from up to 5000 trainees that are not deleted.
Response: 200. Items are { id, value }, both set to the tag.
Errors: none beyond auth.
GET /v1/automation/rpc/form-fields
Lists the fields a coach fills when sending one document to sign. It feeds the fieldValues input of the form assign module.
Auth: studio API key.
string
A
PDF_SIGNATURE form id. Without it, for an unknown id, or for any other form type, the result is an empty array.value is the field key and label is the field label followed by the key in brackets.
Differences from the older notes
backend/docs/AUTOMATION_API.md is behind the code in these places:
- It says there are no triggers. The lane now has webhook registration (
/hooks) with theFORM_FILLEDevent. - It lists five dropdown sources. There is a sixth,
/rpc/form-fields. forms/assignalso acceptsfieldValuesand can return asigningUrlfor documents to sign.- It lists 401, 409 and 429. The handler also maps 403 and 503, and turns every not-found into 400.
form-responsesandweightslists are not fully paginated: they returnitemsonly.