Documentation Index
Fetch the complete documentation index at: https://docs.actionlayer.dev/llms.txt
Use this file to discover all available pages before exploring further.
The rules engine evaluates a condition tree against trigger events and runs actions when conditions match. Rules are how you auto-approve trusted contacts, flag urgent threads, route by sender, or shortcut the approval step for known cases.
| Method | Path | Purpose |
|---|
POST | /v1/rules | Create a rule |
GET | /v1/rules | List rules |
GET | /v1/rules/{id} | Get one rule |
PATCH | /v1/rules/{id} | Update a rule |
DELETE | /v1/rules/{id} | Delete a rule |
How rules work
- An event fires (e.g.
email.received).
- ActionLayer fetches all
active rules for that workspace + trigger, ordered by priority ascending.
- For each rule, it walks the condition tree. If conditions pass, all
actions execute.
- If
stop_after_match: true on a matching rule, evaluation halts; otherwise the next rule is checked.
Rule evaluation runs in the background (Celery) — it never blocks an API response.
Trigger events
| Event | Fires when |
|---|
email.received | An inbound message is parsed and stored |
email.sent | An outbound message is queued for delivery |
email.delivered | Postmark confirmed delivery |
draft.created | An agent submits a draft |
draft.approved | A draft is approved (manually or via auto-approve) |
draft.sent | An approved draft was successfully delivered |
draft.stale | A draft was marked stale (newer inbound arrived) |
Conditions
Conditions are an AND/OR tree:
{
"operator": "AND",
"conditions": [
{ "field": "contact.status", "op": "equals", "value": "trusted" },
{
"operator": "OR",
"conditions": [
{ "field": "message.body_text", "op": "contains", "value": "urgent" },
{ "field": "thread.subject", "op": "contains", "value": "ASAP" }
]
}
]
}
Supported fields
Available fields depend on the trigger:
| Trigger | Fields |
|---|
email.received | message.from_email, message.subject, message.body_text, thread.subject, thread.status, contact.status, contact.tag, contact.domain, identity.id |
draft.created / draft.approved / draft.stale | thread.subject, thread.status, contact.status, contact.tag, contact.domain, identity.id |
Supported operators (op)
| Operator | Applies to | Notes |
|---|
equals | string, enum | Exact match (case-insensitive for emails) |
not_equals | string, enum | |
contains | string | Substring, case-insensitive |
not_contains | string | |
starts_with / ends_with | string | |
in / not_in | string, enum | value must be a list |
The condition builder in the dashboard surfaces only the operators valid for each field.
Actions
actions is an ordered list. Each item is a {type, ...params} object.
| Type | Params | Effect |
|---|
set_thread_status | status: open|waiting|draft_pending|closed | Force the thread into that state |
set_approval_mode | mode: manual|auto | Override auto_approve_replies for this draft only |
skip_approval | (none) | Approve the draft and queue send |
notify_channel | channel: telegram|slack|email, config: {...} | Fire an ad-hoc notification |
add_tag | tag: string | Tag the contact |
set_priority | priority: low|normal|high|urgent | Surface in inbox sorting |
Create
curl -X POST https://api.actionlayer.dev/v1/rules \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Auto-approve trusted contacts",
"trigger_event": "draft.created",
"priority": 50,
"stop_after_match": true,
"conditions": {
"operator": "AND",
"conditions": [
{ "field": "contact.status", "op": "equals", "value": "trusted" }
]
},
"actions": [
{ "type": "skip_approval" }
]
}'
Request body
| Field | Type | Default | Notes |
|---|
name | string | required | Human label |
trigger_event | enum | required | One of the events above |
conditions | object | {} | AND/OR tree, max 10 KB serialized |
actions | array | [] | Max 10 KB serialized |
status | active | inactive | active | inactive keeps the rule but skips evaluation |
priority | int | 100 | Lower number = evaluated first |
stop_after_match | bool | false | If true and the rule matches, no further rules run |
identity_id | UUID | null | Scope rule to a single identity |
assistant_id | UUID | null | Scope rule to a single agent |
More examples
Flag urgent threads
{
"name": "Flag urgent",
"trigger_event": "email.received",
"conditions": {
"operator": "OR",
"conditions": [
{ "field": "message.subject", "op": "contains", "value": "URGENT" },
{ "field": "message.body_text", "op": "contains", "value": "asap" }
]
},
"actions": [
{ "type": "set_priority", "priority": "urgent" },
{ "type": "notify_channel", "channel": "telegram", "config": { "chat_id": "…" } }
]
}
Auto-close newsletter threads
{
"name": "Close newsletters",
"trigger_event": "email.received",
"conditions": {
"operator": "AND",
"conditions": [
{ "field": "message.from_email", "op": "ends_with", "value": "@substack.com" }
]
},
"actions": [
{ "type": "set_thread_status", "status": "closed" }
]
}
List
curl 'https://api.actionlayer.dev/v1/rules?trigger_event=draft.created&status=active' \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY"
Returned in priority order (ascending).
Update
curl -X PATCH https://api.actionlayer.dev/v1/rules/RULE_ID \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "inactive"}'
Delete
curl -X DELETE https://api.actionlayer.dev/v1/rules/RULE_ID \
-H "Authorization: Bearer $ACTIONLAYER_API_KEY"
Errors
| HTTP | error | When |
|---|
| 402 | plan_limit_reached | Workspace at rules cap (0 / 25 / 100 by plan; Builder Pack has no rules) |
| 404 | not_found | Rule does not exist |
| 422 | validation | trigger_event not allowed, conditions/actions over 10 KB, etc. |