The Perform app for Make is a set of JSON definition files in the backend repo. It is a thin wrapper over the Automation API: every module is one HTTP call to /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 under backend/docs/make-app/: Related documents in backend/docs/:
The README says plainly that nothing in the app was probed against the live API. It was written from the Zod schemas, the routes and the repository selects. Run the smoke tests below before handing the app to a coach.

Base

base.json sets what every module inherits:
  • baseUrl is the production automation lane, https://perform-api.otherwise.co.il/v1/automation.
  • The Authorization header is Bearer plus the connection’s API key. The header is listed in log.sanitize so Make does not print it in execution logs.
  • response.valid is body.success. A body that says success: false is a failure whatever status code carried it.
  • response.error.message passes body.message through 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 to ConnectionError, 409 to DuplicateDataError, 429 to RateLimitError, and 500 and 503 to RuntimeError. 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”.
The README and modules/send-a-form.json both refer to a sixth RPC, formFields, backed by GET /rpc/form-fields. The endpoint exists in the API, but there is no rpcs/form-fields.json in the folder. Importing as is leaves the field picker in Send a Form unresolved. Create the RPC by hand: type Dynamic Options, name formFields, calling /rpc/form-fields with formTemplateId taken from the module’s selected form.

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 /hooks with event: FORM_FILLED, Make’s generated webhook.url as targetUrl, and the chosen form. It stores the returned id as hookId.
  • Detach calls DELETE /hooks/:id with 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_filled payload: event, occurredAt, studioId, submissionNumber, formType, documentUrl, form, trainee, response.
Make does not verify the 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.
With the Make CLI or the VS Code extension the same JSON goes into the matching .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 module typeId 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

  1. 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.
  2. Open the Plan dropdown. If it fills with plan names, the base URL, the auth header and the RPCs are wired correctly.
  3. Add the trigger to a blank scenario, leave Form empty and save. Confirm a hook row with GET /v1/automation/hooks.
  4. Submit a form as a trainee. The scenario should run within a second or two.
  5. Delete the trigger and confirm the hook row is gone.

Updating the app

When the automation API changes:
  1. Change the Zod schema and service in apps/core-api/src/modules/automation-api.
  2. Update the matching file under backend/docs/make-app/: expect for inputs, interface for outputs.
  3. Record the change in MAKE_MODULE_CHANGES.md.
  4. Paste the changed tabs into the Make editor. Scenarios already built keep the old field mapping until a coach reopens the module.
Changing a field’s default is a behaviour change for existing scenarios. A scenario built when Send a Form defaulted 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.md says 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 that status on Update a Task rejects an empty string. Neither is true any more: createTraineeBody has no notify field, and updateTaskBody.status goes through blankToUndefined. If the Create a Trainee module still exposes a notify field, it does nothing.