# Business Rules — what you can trigger on

A script never runs on its own. It is the **Invoke Web Service** action of a Business Rule, so the
rule decides *when* it runs and *what data it gets*. Advising on the rule is half the job.

Rules are called **runtime policies** internally, which is why the API and support tooling talk
about "policies". Board settings → **Business Rules**.

A rule is three parts:

```
TRIGGER            when something happens to a card
  + CONDITIONS     …and the card matches this filter
  + ACTIONS        …then do these things   (one of which can be: invoke web service)
```

## Triggers, and which ones can call a script

**Invoke Web Service is not available on every trigger.** Nine of them support it. Checking this
first saves designing a solution that cannot be wired up.

| Trigger | Calls a script? |
|---|---|
| Card is moved | **yes** |
| Card is created | **yes** |
| Card is updated | **yes** |
| Card updated by email | **yes** |
| Time-based ("When" — a date field is N units away) | **yes** |
| Recurring update cards (scheduled, hourly at most) | **yes** |
| WIP limit is reached | **yes** |
| WIP limit is exceeded | **yes** |
| Card count | **yes** |
| Child / parent / relative / predecessor / successor card **is moved** | no |
| Child / parent / relative / predecessor / successor card **is updated** | no |
| Child card is blocked · All children are unblocked | no |
| Recurring **create** cards | no |
| Recurring **archive** cards | no |

There is no card-level "card is blocked" trigger — blocking is only exposed as *child card is
blocked* and *all children are unblocked*, and neither can call a script.

### When the trigger you want cannot call a script

Trigger on the card you *can* reach, then walk the relationship inside the script. "Do something
when the parent is updated" becomes: trigger on **Card is updated** for the child, then read the
parent through the API.

```
var links = [Links];
var parentId = 0;
foreach (links as i => link) {
    if (link->link_type == "parent") { parentId = link->card_id; }
}
if (parentId == 0) { exit("no parent"); }
```

The other route is a two-step: a native action on the unsupported trigger sets a marker (a tag, a
sticker, a custom field), and a second rule triggers on **Card is updated** and calls the script.
Costs a rule, but keeps the logic in one place.

## Conditions

One mechanism, despite appearances: the condition is a **card search filter** evaluated against the
card that fired the trigger. The editor phrases it per trigger — "the created card matches this
filter", "the moved card matches this filter", "the updated card matches this filter" — but it is
the same filter builder each time, over the standard card fields and custom fields.

Push as much selection as possible into the condition rather than into the script:

- The rule does not fire at all, so nothing is logged and no execution row is created.
- The script stays about the work, not about deciding whether to do it.
- Filters are far cheaper than an API call inside a script.

Keep in the script only what a filter cannot express — anything needing a second card's data, an
external system, or arithmetic across fields.

## Actions

Do not write a script for something the platform already does. The native actions are:

| | |
|---|---|
| **Move** | move the card · move the updated card · move its parent · move their parent · move its child cards · move its predecessor cards · move its successor cards |
| **Update** | update the card details · update the child / parent / relative / predecessor / successor cards details |
| **Create** | create cards · create cards or subtasks · convert subtasks to |
| **Lifecycle** | archive cards · block parent · unblock parent |
| **Notify** | send notifications |
| **Other** | run AI agent · **invoke web service** · execute at |

Reach for Code Runner when the requirement needs a loop, a decision the filter cannot express, data
from more than one card, or a call to an external system. A rule can have several actions, so a
native action and a script can share a rule.

## The Invoke Web Service form

The action opens **Service Invoke Settings**. Fields, in the order they appear:

| Field | For Code Runner |
|---|---|
| Name | Free text, for the rule list. Name it after the script's job. |
| Url | `https://solutions.businessmap.io/codeRunner` |
| Method | **POST** — see below |
| Authentication | **API KEY**, header name `apikey`, value = the account's API key |
| Headers | `subdomain` = the account subdomain; `domain` = `businessmap.io` if the account is on that domain |
| Send the parameters in the body | Must end up in the body — `source` is read as a POST field |
| Parameters | `business_rule_id` = the rule's ID, `source` = the script |

**The method must be POST.** With GET (or HEAD or OPTIONS) the editor discards every parameter
before sending, so `source` never leaves Businessmap — and because `GET /codeRunner` is a real
route that serves the tool's landing page, the rule receives a `200` and records the run as
**successful**. Nothing executes, and nothing appears in the execution log. A rule that reports
success while doing nothing at all is almost always this.

**Test Settings** in that dialog fires the request immediately. It is the fastest way to prove the
URL, headers and auth are right before involving the trigger.

## Where pills can go

Three fields accept pills, and they do not offer the same ones:

| Field | Pills available |
|---|---|
| **Parameter values** (this is where `source` lives) | The full catalogue — every regular pill plus every custom field on the account |
| **Url** | Only three: `Card Id`, `Custom Card Id`, `Internal Card Id` |
| **Header values** | **None.** Header values are plain text |

That last row is why a secret cannot be assembled from card data in a header, and why the masked
`multipart` header in SETUP.md is a fixed string.

See SETUP.md for the pill catalogue and the quoting rules — those decide whether a script parses.
