/v1/automation, and the one trigger is a registered outbound webhook.
Nothing in the backend runs for Make specifically. If the automation lane works, the app works.
Files
All underbackend/docs/make-app/:
Related documents in
backend/docs/:
Base
base.json sets what every module inherits:
baseUrlis the production automation lane,https://perform-api.otherwise.co.il/v1/automation.- The
Authorizationheader isBearerplus the connection’s API key. The header is listed inlog.sanitizeso Make does not print it in execution logs. response.validisbody.success. A body that sayssuccess: falseis a failure whatever status code carried it.response.error.messagepassesbody.messagethrough unchanged. Those messages are written for a coach reading a Make execution log, and replacing them with “Bad Request” would throw away the only thing that tells the user what to fix. The only text the app adds is a fallback for a response with no message at all, such as a proxy error.- Per-status blocks map 400 to
DataError, 401 and 403 toConnectionError, 409 toDuplicateDataError, 429 toRateLimitError, and 500 and 503 toRuntimeError. These type names only affect how Make labels and retries a failure.
Connection
connection.json defines one parameter, apiKey, a studio API key. The coach creates it in the coach web app’s integrations settings. It is shown once.
Validation calls GET /v1/automation/validate. On success Make names the connection after the studio (response.metadata reads body.data.studioName), which is how a coach with two studios tells two connections apart. A wrong key comes back with the API’s own message, “invalid partner api key”.
Dropdown sources
Modules
25 modules. Paths are relative to the base URL.Actions
Searches
Trigger
Its label in the file is in Hebrew. It fires when a trainee submits a form.
The instant trigger
webhooks/form-filled.json defines the formFilled webhook with one optional parameter, formTemplateId, fed by the forms RPC. Leave it empty to fire for every form.
- Attach calls
POST /hookswithevent: FORM_FILLED, Make’s generatedwebhook.urlastargetUrl, and the chosen form. It stores the returned id ashookId. - Detach calls
DELETE /hooks/:idwith that stored id. This is the only way Make can clean up after itself. - The webhook’s output is the whole request body, so the module’s interface matches the
form_filledpayload:event,occurredAt,studioId,submissionNumber,formType,documentUrl,form,trainee,response.
X-Perform-Signature header in this definition. The generated webhook URL is the secret.
Things to know when a scenario does not fire:
- Delivery is queued. The scenario runs a moment after the submission.
- A receiver that is down is retried five times with exponential backoff.
- A target that answers 4xx is dropped at once. That is what Make returns after a scenario is deleted without detaching, so the stale hook stops being retried but its row stays until deleted. Check
GET /v1/automation/hooks.
Design decisions in the modules
These are deliberate. Keep them when you edit a module.
Two output shapes were resolved from code and should be confirmed with one live call each: Assign a Program returns the full
Program row including its content JSON, and Send a Form returns the full FormAssignment row. A select added to those repository calls later would silently shrink them.
Importing
Make’s custom app editor is tab-based. Each file is a bundle of the JSON that goes in each tab. Import in this order, because each step depends on the one before.1
Create the app
Name it
Perform. Paste base.json into the Base tab.2
Create the connection
Type API Key (Make calls it
basic), named perform. The parameters array goes in the Parameters tab and the api object in the Communication tab.3
Create the RPCs
Type Dynamic Options. Use the file’s
name as the RPC name and paste communication into the Communication tab. Do this before any module, because modules reference RPCs by name.4
Create the webhook
Type Web Hook, name
formFilled, connection perform. Paste communication into API, parameters into Parameters, attach into Attach and detach into Detach. Leave Update and Scope empty.5
Create the modules
One per file. Use the file’s
name, label, description, type and connection. Paste communication, expect (mappable parameters), parameters (static, always empty here), interface, and for the trigger samples. Start with Create a Trainee, which exercises the connection, the base error mapping and two RPCs at once..imljson files under connections/, rpcs/ and modules/.
Keys to check against Make’s schema
The README lists a handful of keys that were chosen from how Make apps are conventionally written, not derived from this repo: the moduletypeId numbers, the per-status error blocks and their type names, mappable on RPC-backed selects, the text type on the API key parameter, encodeURL in the find-by-phone URL, log.sanitize, and the placement of webhook, attach and detach. If Make rejects one you will see it at import or on the first run. All of them are cosmetic. None changes a request.
Smoke tests
- Add Create a Trainee to a blank scenario and add the connection with a real key. The connection should save under the studio’s name.
- Open the Plan dropdown. If it fills with plan names, the base URL, the auth header and the RPCs are wired correctly.
- Add the trigger to a blank scenario, leave Form empty and save. Confirm a hook row with
GET /v1/automation/hooks. - Submit a form as a trainee. The scenario should run within a second or two.
- Delete the trigger and confirm the hook row is gone.
Updating the app
When the automation API changes:- Change the Zod schema and service in
apps/core-api/src/modules/automation-api. - Update the matching file under
backend/docs/make-app/:expectfor inputs,interfacefor outputs. - Record the change in
MAKE_MODULE_CHANGES.md. - Paste the changed tabs into the Make editor. Scenarios already built keep the old field mapping until a coach reopens the module.
notify to false keeps sending false until the coach flips it.
Stale notes in the existing docs
Verified against the code:MAKE_MODULE_AGENT_PROMPT.mdsays every response is HTTP 200. Failures carry real status codes.- The same file has a section titled “There are no triggers”. There is one now.
- The README’s “Two API gotchas” section says Create a Trainee accepts and ignores
notify, and thatstatuson Update a Task rejects an empty string. Neither is true any more:createTraineeBodyhas nonotifyfield, andupdateTaskBody.statusgoes throughblankToUndefined. If the Create a Trainee module still exposes anotifyfield, it does nothing.