Docs / Getting started
Your first automation
You build one automation in the browser: a shop posts an order, a script finds the customer in a CRM — or creates them — and answers with what it did. It is small on purpose: every piece of the platform you will use for the rest of your work appears in it exactly once.
What you need: a sign-in, a sandbox environment you may edit, and an idea
of what a REST call is — a method, a path, a JSON answer. No programming beyond
six lines of script. The CRM address and key below are placeholders; use your
own system the second time. (Running the product yourself and nobody has
signed in yet? Read On your own installation
first.)
Everything happens in the sandbox, and everything below is undone by deleting what you created. On the product’s website each step has a picture whose marked controls carry the step’s numbers; the help inside the product shows the same page without pictures.
1 · Open your sandbox


- Tenant — the tenant you work in.
- Sandbox — the environment switch in the title bar. Production looks different on purpose: a coloured band across the top, and a confirmation on anything that changes it.
- + (New entity) above the explorer — every connection, function and script starts here.
The first change you make takes the environment’s edit lock, shown in the same bar. One browser window edits an environment at a time; anyone else sees your name and reads along. Hand it back from the badge when you are done — a lock you are still holding is also what stops the command line from writing.
2 · The connection to the CRM
A connection is where a system lives and who you are to it.


Press + and choose Connection (1). It appears straight away, with a placeholder name and its form open.


- Display name — type
CRMover the placeholder. - Base URL —
https://crm.example.com/v2, the address every call is made relative to. - Outgoing authentication — choose API key, leave Where the key
goes on A request header and enter the Header name the far end
expects, for example
X-API-Key. If your far end documents its key as part of the address instead — something like?api_key=…— choose A query parameter and name it there. - API key — paste it.
- Activate.
Activate is the only save. Everything you changed since the last one — this form, another form, a script, a trigger — is checked and written together, or nothing is. That is why there is no save button on each field, and why you can move between entities mid-edit without losing anything.
The key is encrypted before it is stored, and it never comes back: reopen the form and you see a fixed mask, not your key. Leaving that mask alone keeps the stored key; typing over it replaces it. No script can read it — a script names the connection, and the platform applies the credential when the call goes out.
The name a script types
Every entity has two names. You type the display name; the platform derives the name from it, and the name is the one that travels:
| You typed | The name | Because |
|---|---|---|
CRM | crm | lower case, hyphens for anything else |
Order webhook | order-webhook | the same rule |
Find contact | FindContact | a function’s name has to be callable in a script |
The name is what fetch({ connection: "crm" }) speaks, what a file in a folder
refers to another entity by, what a promotion to production matches the two
sides up with, and what a health signal or an alert names when something is
failing. The display name is a label; the name is the identity. The entity’s
header shows it beside the display name.
Rename an entity and the derived name follows, so anything that spelled out the old one — a script calling a function by name, a file on disk — is yours to update.
3 · The address the order arrives at
The shop needs somewhere to POST to. Create a second connection the same way,
Shop, give it the shop’s own address as Base URL, and under Endpoints
press Add endpoint.


- Display name —
Shop. - Path —
orders. - The URL the shop posts to:
/hooks/<your tenant>/sandbox/orderson your endpoints’ address. Copy it into the shop’s webhook settings later.
(When you want to POST to it yourself, samples/webhook.sh
is that call with nothing to remember.)
That address is public, so decide who may call it. Incoming authentication
on the endpoint is what a caller has to present — a shared secret, a signature
the sender computes, or a known sender’s own verification scheme. none is fine
while you are trying things out in sandbox and is not fine afterwards.
Activate.
4 · The function that finds a contact
A function is one operation, configured once, in your own vocabulary.
Press + → Function and name it Find contact; a script calls it as
FindContact.


- Connection —
CRM. - Method & path —
GETand/contacts. - Add query parameter — name
email, value{{email}}. The double braces are filled from a parameter. - Add parameter —
email. This is what a script passes in.
Now say what the call answers. Open Advanced and its tab The answer:


- Advanced → The answer.
- Accept these statuses —
200. - Take the value at —
contacts. - Take the one record found (refuse if there are several) — switch it on.
- When nothing is found — choose Answer nothing — absence is an ordinary result.
That block is worth the minute it takes. Because you declared it, the call
answers a contact — not an envelope you unwrap, not a status you check — or
null when there is none; any other status stops the run and says what came
back. The line at the top of the form keeps up as you type.
Try it before you save it:


- Test arguments —
{ "email": "ada@example.com" }. - Test.
- The result: What was sent (the credential named, never shown) and exactly what a script would receive. Testing changes nothing — it runs the definition in front of you, saved or not, and the request it shows you is shown once and never stored.
Activate.
5 · The function that creates a contact
One more, with the fields you just used. + → Function, named
Create contact:
| Field | Value |
|---|---|
| Connection | CRM |
| Method & path | POST, /contacts |
| Add parameter (twice) | email, name |
| Body | JSON |
| Request body (JSON) | {"email":"{{email}}","name":"{{name}}"} |
| Advanced → The answer → Accept these statuses | 200, 201 |
| Advanced → The answer → Take the value at | id |
Activate.
6 · The script
+ → Main script, named on-order. Paste this into the editor:
const order = input.event.body;
const found = FindContact({ email: order.customer });
const contactId = found === null
? CreateContact({ email: order.customer, name: order.customer })
: found.id;
log("order matched to a contact", { order: order.id, contact: contactId });
return { order: order.id, contact: contactId };


- Display name —
on-order. - The editor. Your environment’s own functions are in scope: start typing
FindandFindContactcompletes, with its parameters and what it answers.
Six lines, and none of them is about HTTP. No await, no client to build, no
token, no retry loop, no status check: FindContact answers a contact or
null, and if the CRM had answered something unexpected the run would have
stopped right there saying so.
7 · What starts the script
In the same form, under Triggers:


- Trigger type — Webhook.
- Connection —
Shop. - Endpoint —
orders. - Add trigger.
Activate.
8 · Run it without leaving the page
The run panel beside the editor runs your script with an input you type — your unsaved work included, so you can try a change before you activate it.


-
Test input — the order as the shop would send it:
{ "id": "o-1042", "customer": "ada@example.com", "items": [{ "sku": "A-1", "price": 20 }] } -
Test.
-
The run: what it returned, what it cost, and every line it logged — including the object you passed as
log’s second argument.
9 · Then for real
Point the shop’s webhook at the URL from step 3, or post an order yourself with
samples/webhook.sh. The response comes back immediately —
accepting an event and running the script are two different things, and the
caller is not kept waiting for yours. (If you do want the caller to wait for
an answer, that is the endpoint’s other door: an API service trigger, and
respond() in the script — Answering a caller.)
Then open the run in the script’s run log: the payload as it arrived, the calls that went out, the answers, the lines, the result. Everything in Reading what triggered you comes from what you see there.
On your own installation
If you run the product yourself — on your own machine
or on a box — and you are the first person here, there
is no account yet. The server’s startup log prints a one-time setup token
(it also writes it into the data root), and the sign-in page sends you to a
setup form that takes it and creates the first administrator. That first run
also creates a tenant called Default, with a sandbox and a production
environment.
A fresh administrator has no access to anything yet — access is granted, never
assumed, even for the person who installed it. The explorer says No
environment yet and offers Create your first tenant, which opens the
Administration area. Open Default, press Grant myself on sandbox, then
Back to authoring — and start at step 1.
What to read next
- Build with Claude Code — describe the next automation and let an assistant build it in your sandbox.
- Working from a terminal — the same work as a folder of files and commands.
- The language — what you may write in that editor.
- Connections and functions — the other five kinds of function, and how to make a call safe to repeat.
- Reacting to failures — because nobody is watching at 02:00.