a9script

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

Signed in: the tenant, the sandbox chosen in the environment switch, and the New entity button.Signed in: the tenant, the sandbox chosen in the environment switch, and the New entity button.
Signed in: the tenant, the sandbox chosen in the environment switch, and the New entity button.
  1. Tenant — the tenant you work in.
  2. 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.
  3. + (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.

New entity opens the list of kinds; Connection is the first.New entity opens the list of kinds; Connection is the first.
New entity opens the list of kinds; Connection is the first.

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

The CRM connection: its name, the base URL every call is made relative to, and the API key sent as a header.The CRM connection: its name, the base URL every call is made relative to, and the API key sent as a header.
The CRM connection: its name, the base URL every call is made relative to, and the API key sent as a header.
  1. Display name — type CRM over the placeholder.
  2. Base URL — https://crm.example.com/v2, the address every call is made relative to.
  3. 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.
  4. API key — paste it.
  5. 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 typedThe nameBecause
CRMcrmlower case, hyphens for anything else
Order webhookorder-webhookthe same rule
Find contactFindContacta 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.

The shop's connection with an endpoint at the path orders, and the URL the shop posts to.The shop's connection with an endpoint at the path orders, and the URL the shop posts to.
The shop's connection with an endpoint at the path orders, and the URL the shop posts to.
  1. Display name — Shop.
  2. Path — orders.
  3. The URL the shop posts to: /hooks/<your tenant>/sandbox/orders on 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.

The FindContact function: the connection, GET on /contacts, a query parameter filled from the email parameter.The FindContact function: the connection, GET on /contacts, a query parameter filled from the email parameter.
The FindContact function: the connection, GET on /contacts, a query parameter filled from the email parameter.
  1. Connection — CRM.
  2. Method & path — GET and /contacts.
  3. Add query parameter — name email, value {{email}}. The double braces are filled from a parameter.
  4. Add parameter — email. This is what a script passes in.

Now say what the call answers. Open Advanced and its tab The answer:

What the call answers: status 200, the value at contacts, the one record found, and nothing when there is none.What the call answers: status 200, the value at contacts, the one record found, and nothing when there is none.
What the call answers: status 200, the value at contacts, the one record found, and nothing when there is none.
  1. Advanced → The answer.
  2. Accept these statuses — 200.
  3. Take the value at — contacts.
  4. Take the one record found (refuse if there are several) — switch it on.
  5. 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 with an e-mail address: the request that was sent and exactly what a script receives.Test with an e-mail address: the request that was sent and exactly what a script receives.
Test with an e-mail address: the request that was sent and exactly what a script receives.
  1. Test arguments — { "email": "ada@example.com" }.
  2. Test.
  3. 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:

FieldValue
ConnectionCRM
Method & pathPOST, /contacts
Add parameter (twice)email, name
BodyJSON
Request body (JSON){"email":"{{email}}","name":"{{name}}"}
Advanced → The answer → Accept these statuses200, 201
Advanced → The answer → Take the value atid

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 };
The on-order script in the editor: six lines, two configured calls, no HTTP.The on-order script in the editor: six lines, two configured calls, no HTTP.
The on-order script in the editor: six lines, two configured calls, no HTTP.
  1. Display name — on-order.
  2. The editor. Your environment’s own functions are in scope: start typing Find and FindContact completes, 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:

The trigger: a webhook on the shop's orders endpoint starts the script.The trigger: a webhook on the shop's orders endpoint starts the script.
The trigger: a webhook on the shop's orders endpoint starts the script.
  1. Trigger type — Webhook.
  2. Connection — Shop.
  3. Endpoint — orders.
  4. 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.

A test run with a sample order: what the script returned and every line it logged.A test run with a sample order: what the script returned and every line it logged.
A test run with a sample order: what the script returned and every line it logged.
  1. Test input — the order as the shop would send it:

    {
      "id": "o-1042",
      "customer": "ada@example.com",
      "items": [{ "sku": "A-1", "price": 20 }]
    }
  2. Test.

  3. 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.

Rendered from docs/guide/first-automation.md in the product's own repository, at build time. Found a problem on this page? Write to the address in the footer.