Files
wekan/docs/API/Rules.md
T
Lauri Ojansivu 25d33cec36 Fix #2345 #2674 #574: rules card title filter, rules REST API, api docs;
Sandstorm username races.

- #2345: the Card Title Filter is now SAVED by every board trigger
  builder (a trigger doc missing the field could never satisfy the
  matcher's $in - those rules fired for nothing), SHOWN in the rule
  details, and ENFORCED for archive/restore triggers by resolving the
  title from the card when the activity lacks it; legacy field-less
  triggers keep matching; the filter popup prefills its current value.
- #2674: rules created via POST/PUT /api/boards/:boardId/rules never
  fired - missing matching fields are now normalized to '*' wildcards
  and trigger/action types validated; the addMember/removeMember rule
  actions no longer crash on unresolvable usernames or member-less
  cards and their writes are awaited. New docs/API/Rules.md (endpoints,
  matching fields, wildcard semantics, the exact two-rule
  add-on-move-to/remove-on-move-from workflow), linked from REST-API.md;
  api.py help now lists the rule commands with the same example.
- #574: Sandstorm unique-username generation probes case-insensitively
  (anchored, regex-escaped) and retries the next number when a
  concurrent insert wins the unique-index race, instead of aborting the
  login hook with E11000.

Tests: tests/rulesCardTitleFilter.test.cjs (12),
tests/rulesApiTriggerNormalize.test.cjs (19),
tests/sandstormUsername.test.cjs (11) - positive and negative cases.

Thanks to InfoSec812, sfahrenholz, mitar and xet7.
2026-07-17 03:36:50 +03:00

186 lines
7.2 KiB
Markdown

# Rules (Board Automation / IFTTT) REST API
Board automation rules ("IFTTT rules") link a **trigger** (an event on the
board) to an **action** (what happens to the card/board). The REST API embeds
the full trigger and action inline, so a rule is self-contained.
This API makes the workflow of
[issue #2674](https://github.com/wekan/wekan/issues/2674) scriptable: add a
user to a card when it is moved **to** a list, and remove the user again when
the card is moved **away from** that list.
## Summary
| HTTP Method | Url | Short Description |
| :--- | :--- | :--- |
| `GET` | `/api/boards/:boardId/rules` | List all automation rules of a board |
| `GET` | `/api/boards/:boardId/rules/:ruleId` | Get one rule (with its trigger and action) |
| `POST` | `/api/boards/:boardId/rules` | Add an automation rule |
| `PUT` | `/api/boards/:boardId/rules/:ruleId` | Edit a rule's title, trigger and/or action |
| `DELETE` | `/api/boards/:boardId/rules/:ruleId` | Remove a rule (and its trigger and action) |
All endpoints require a `Bearer` token; write endpoints require board write
access (BoardAdmin).
## Trigger matching and the `*` wildcard
The rule engine matches an incoming board activity against **every** matching
field of the trigger's `activityType` (for example `moveCard` matches on
`boardId`, `listName`, `oldListName`, `userId`, `swimlaneName`, `cardTitle`).
A field set to `'*'` matches anything.
You only need to supply the fields you want to constrain: any matching field
missing from the trigger you POST/PUT is stored as the `'*'` wildcard
automatically. (Before this normalization existed, an API-created trigger
that omitted a field such as `userId` could never match any activity, so the
rule silently never fired — the bug reported in issue #2674.)
### Trigger `activityType` values
| `activityType` | fires when | matching fields |
| :--- | :--- | :--- |
| `createCard` | a card is created | `boardId`, `listName`, `userId`, `swimlaneName`, `cardTitle` |
| `moveCard` | a card is moved to/from a list or swimlane | `boardId`, `listName` (destination), `oldListName` (source), `userId`, `swimlaneName`, `cardTitle` |
| `archivedCard` / `restoredCard` | a card is archived / unarchived | `boardId`, `userId`, `cardTitle` |
| `joinMember` / `unjoinMember` | a member is added to / removed from a card | `boardId`, `username`, `userId` |
| `addChecklist` / `removeChecklist` / `completeChecklist` / `uncompleteChecklist` | checklist events | `boardId`, `checklistName`, `userId` |
| `addedChecklistItem` / `removedChecklistItem` / `checkedItem` / `uncheckedItem` | checklist item events | `boardId`, `checklistItemName`, `userId` |
| `addAttachment` / `deleteAttachment` | attachment events | `boardId`, `userId` |
| `addedLabel` / `removedLabel` | label events | `boardId`, `labelId`, `userId` |
| `scheduledTrigger` | time-based (due/aging/recurring); see `scheduleKind`, `days`, `atTime` | evaluated by the scheduler, not by activities |
For a card moved **to** a list, constrain `listName` (the destination list's
title). For a card moved **away from** a list, constrain `oldListName` (the
source list's title) and leave `listName` as `'*'`.
### Action `actionType` values
`moveCardToTop`, `moveCardToBottom`, `sendEmail`, `setDate`, `updateDate`,
`removeDate`, `setDateRelative`, `archive`, `unarchive`, `setColor`,
`addLabel`, `removeLabel`, `addMember`, `removeMember`, `checkAll`,
`uncheckAll`, `checkItem`, `uncheckItem`, `addChecklist`, `removeChecklist`,
`addChecklistWithItems`, `addSwimlane`, `createCard`, `linkCard`,
`markCardComplete`, `markCardIncomplete`, `sortList`, `moveAllCardsInList`.
The member actions take a `username`:
* `{"actionType": "addMember", "username": "someuser"}` — add the user to the card
* `{"actionType": "removeMember", "username": "someuser"}` — remove the user from the card
* `{"actionType": "removeMember", "username": "*"}` — remove **all** members from the card
## List rules
```shell
curl -H "Authorization: Bearer TOKEN" \
-X GET \
http://localhost:3000/api/boards/BOARDID/rules
```
## Get one rule
```shell
curl -H "Authorization: Bearer TOKEN" \
-X GET \
http://localhost:3000/api/boards/BOARDID/rules/RULEID
```
Result example:
```json
{
"_id": "8gDW2AtwbpNbqSyEw",
"title": "Remove member when moved away from Doing",
"trigger": {
"activityType": "moveCard",
"listName": "*",
"oldListName": "Doing",
"swimlaneName": "*",
"cardTitle": "*",
"userId": "*"
},
"action": {
"actionType": "removeMember",
"username": "someuser"
}
}
```
## Add a rule
```shell
curl -H "Authorization: Bearer TOKEN" \
-H "Content-type: application/json" \
-X POST \
http://localhost:3000/api/boards/BOARDID/rules \
-d '{
"title": "Add member when moved to Doing",
"trigger": { "activityType": "moveCard", "listName": "Doing" },
"action": { "actionType": "addMember", "username": "someuser" }
}'
```
### Full example: issue #2674 workflow
Add the user when a card is moved **to** the list "Doing", remove the user
again when the card is moved **away from** "Doing":
```shell
# Rule 1: card moved TO "Doing" -> add member
curl -H "Authorization: Bearer TOKEN" -H "Content-type: application/json" -X POST \
http://localhost:3000/api/boards/BOARDID/rules \
-d '{ "title": "Add member on move to Doing",
"trigger": { "activityType": "moveCard", "listName": "Doing" },
"action": { "actionType": "addMember", "username": "someuser" } }'
# Rule 2: card moved AWAY FROM "Doing" -> remove member
curl -H "Authorization: Bearer TOKEN" -H "Content-type: application/json" -X POST \
http://localhost:3000/api/boards/BOARDID/rules \
-d '{ "title": "Remove member on move from Doing",
"trigger": { "activityType": "moveCard", "oldListName": "Doing" },
"action": { "actionType": "removeMember", "username": "someuser" } }'
```
The omitted trigger fields (`listName` in rule 2, `oldListName` in rule 1,
plus `userId`, `swimlaneName`, `cardTitle`) are stored as `'*'` wildcards.
## Edit a rule
Any of `title`, `trigger` and `action` may be supplied; a supplied trigger or
action document replaces the stored fields. When the supplied trigger includes
an `activityType`, its missing matching fields are defaulted to `'*'` too.
```shell
curl -H "Authorization: Bearer TOKEN" \
-H "Content-type: application/json" \
-X PUT \
http://localhost:3000/api/boards/BOARDID/rules/RULEID \
-d '{ "action": { "actionType": "removeMember", "username": "*" } }'
```
## Delete a rule
Removes the rule together with its trigger and action documents.
```shell
curl -H "Authorization: Bearer TOKEN" \
-X DELETE \
http://localhost:3000/api/boards/BOARDID/rules/RULEID
```
## Python CLI (api.py)
The bundled `api.py` exposes the same capability:
```shell
python3 api.py listrules BOARDID
python3 api.py getrule BOARDID RULEID
python3 api.py addrule BOARDID 'TITLE' 'TRIGGER_JSON' 'ACTION_JSON'
python3 api.py editrule BOARDID RULEID 'PATCH_JSON'
python3 api.py removerule BOARDID RULEID
# Issue #2674: remove a user from the card when it is moved away from a list
python3 api.py addrule BOARDID 'Remove member on move from Doing' \
'{"activityType":"moveCard","oldListName":"Doing"}' \
'{"actionType":"removeMember","username":"someuser"}'
```