> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Ticket automation — event-driven ticket rules

> Author rules that fire on ticket lifecycle events — created, tagged, idle, status changed, priority changed — and reassign, retag, reprioritize, escalate, or notify, with a dry-run preview before anything is saved.

# Ticket automation — event-driven ticket rules

Ticket automation moves routine triage out of the agent's head and into a rule. You name a lifecycle event a rule listens to — a ticket being created, tagged, going idle, or changing status or priority — gate it with conditions on the ticket fields, and list the actions that run when they hold: reassign or unassign, add or remove a tag, set priority, status, or type, set a custom field, escalate, or notify a Slack channel, an in-app destination, or a webhook. Every saved rule then fires automatically on every ticket that raises that event in your workspace.

The builder lives under **Inbox → Settings → Ticket automation**. Authoring and testing rules requires an owner or admin role; any authenticated operator can read the rule grammar catalog.

The same rule engine completes with [SLA escalation policies](/inbox/sla-escalation-policies): queue-level policies answer *how fast is this queue moving*, ticket automation answers *what should happen to this ticket right now*. The two compose — a rule can be the thing that raises priority or reassigns before a queue breach ever fires.

## Trigger catalog

Each rule subscribes to exactly one trigger. The conditions then filter within the event, and the actions run when the conditions hold.

| Trigger                   | Fires when                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `ticket_created`          | A new ticket is opened — by an agent, by the auto-ticket-on-hangup path, or by the public help-center form.                    |
| `ticket_tagged`           | A tag mutation lands on the ticket.                                                                                            |
| `ticket_idle`             | The ticket has sat untouched for a configured number of minutes. Conditions test the idle length via the `idle_minutes` field. |
| `ticket_status_changed`   | The ticket's status moves (`open`, `pending`, `resolved`, `wont_fix`).                                                         |
| `ticket_priority_changed` | The ticket's priority moves (`low`, `normal`, `high`, `urgent`).                                                               |

Conditions reference a closed set of ticket fields — `priority`, `status`, `type`, `source`, `tags`, `assignee`, `idle_minutes`, `subject`, `requester_email`, and `custom_field` (which requires a `key`). Operators: `eq`, `neq`, `in`, `not_in`, `contains`, `not_contains`, `gte`, `lte`, `is_empty`, `is_not_empty`. Not every operator is valid for every field — `idle_minutes` supports only `gte`/`lte`, for example — and the builder renders the valid set per field from the catalog endpoint. A rule with no conditions is unconditional: it fires on every event of its trigger.

Rules declare `match: "all"` (every condition must hold — the AND) or `match: "any"` (at least one condition holds — the OR). Each rule also carries a numeric `priority` — lower runs first — and a `stop_processing` flag: when a matching rule sets it, no lower-priority rule is evaluated for that event, so a decisive first-match rule is final.

## Action vocabulary

One rule can carry up to ten actions from this vocabulary:

* `assign` / `unassign` — hand the ticket to a named agent or clear the assignee. An automation-driven assign honors the same per-agent channel concurrency caps the manual assign path enforces: if the target agent is already at their ceiling, the assign is skipped and the rest of the rule's actions still land.
* `add_tag` / `remove_tag` — mutate the tag set. Added tags resolve to the curated casing from your tag definitions, so a rule adding "blue" stores the same "Blue" a manual tag apply would.
* `set_priority` / `set_status` / `set_type` — move a field directly. When priority changes this way, the ticket's SLA due windows are re-scaled to the new priority, exactly as a manual priority edit does.
* `set_custom_field` — write one key in the ticket's custom fields.
* `escalate` — a combined move: optionally raise `to_priority` and optionally hand the ticket to `assign_user_id` in one action.
* `notify` — emit a notification to a `slack`, `in_app`, or `webhook` target. Those are the only channels; automation notifications go where your integrations listen, never to the customer's phone.

## The dry-run button

The builder can test a rule before it is saved. Pick a sample ticket — the form lets you set priority, status, type, source, tags, assignee, subject, requester email, custom fields, and minutes idle — then **Run dry-run**. The result shows:

* `matched` — did the rule's conditions hold on that sample;
* `actions` — the fire list, in evaluation order;
* `effect` — the resulting ticket field patch plus the notifications that would go out.

The dry-run mutates nothing — it is the same evaluation core the live executor runs, so a rule that previews one way fires exactly that way in production. Save the rule only once the dry-run shows the outcome you intend.

## API parity — manage rules programmatically

Everything the builder does goes through public endpoints under `/api/v1/inbox/ticket-automation`:

| Endpoint                              | Role                                                                                                                                                                                                    |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /ticket-automation/catalog`      | The static grammar — triggers, per-field operators, action types, and the closed priority/status/type value lists. The builder renders from this; your integration can too. Any authenticated operator. |
| `POST /ticket-automation/test`        | Dry-run an inline rule against a sample ticket and event. Owner/admin.                                                                                                                                  |
| `GET /ticket-automation/rules`        | List persisted rules, ordered by evaluation priority. Owner/admin.                                                                                                                                      |
| `POST /ticket-automation/rules`       | Create a rule. Owner/admin.                                                                                                                                                                             |
| `PATCH /ticket-automation/rules/:id`  | Replace a rule's authored fields. Owner/admin.                                                                                                                                                          |
| `DELETE /ticket-automation/rules/:id` | Delete a rule. Owner/admin.                                                                                                                                                                             |

A rule body mirrors the builder's shape:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/ticket-automation/rules" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "VIP to premium queue",
    "trigger": "ticket_created",
    "priority": 10,
    "match": "all",
    "conditions": [
      { "field": "tags", "operator": "contains", "value": "vip" }
    ],
    "actions": [
      { "type": "assign", "agent_user_id": "user_premium_queue_lead" },
      { "type": "notify", "channel": "slack", "target": "#on-call" }
    ],
    "stop_processing": true
  }'
```

The dry-run endpoint takes the same rule plus the event and a sample ticket:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/ticket-automation/test" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "rule": { "name": "VIP check", "trigger": "ticket_created",
      "conditions": [{ "field": "tags", "operator": "contains", "value": "vip" }],
      "actions": [{ "type": "set_priority", "priority": "urgent" }] },
    "trigger": "ticket_created",
    "sample_ticket": { "tags": ["vip"], "priority": "normal", "status": "open" }
  }'
# -> { "matched": true, "actions": [{ "type": "set_priority", "priority": "urgent" }],
#      "effect": { "patch": { "priority": "urgent" }, "notifications": [], ... } }
```

When a saved rule fires on a real ticket, the workspace records it end to end: a system-authored note appears on the ticket timeline naming the rule and what changed, an audit event is written, and a `ticket.automation_applied` webhook event goes out to your subscribed endpoints carrying the matched rules, the field patch, and the notifications the rule requested. Delivering webhook surfaces is the only outbound automation performs.

## Example recipes

Two shapes cover most desks — route the important ones early, catch the stale ones late.

**VIP customer opens a ticket → premium queue → notify on-call.**

* Trigger: `ticket_created`
* Conditions: `tags contains "vip"` (tag your VIP contacts at intake, or let an earlier rule apply the tag by requester email)
* Actions: `assign` to the premium queue lead, `notify` your on-call Slack channel, `stop_processing` so generic triage rules below it do not run second.

**Ticket sitting unread for two days → escalate priority → tag stale.**

* Trigger: `ticket_idle`
* Conditions: `idle_minutes gte 2880`, `status in ["open", "pending"]`
* Actions: `escalate` with `to_priority: "high"`, `add_tag "stale"`.

Wire the second recipe with an [SLA escalation policy](/inbox/sla-escalation-policies) on the same queue and you get both halves: the ticket itself escalates and self-labels while the queue-level policy pages a supervisor if the backlog keeps aging.

## How automation fits with reply approvals and AI deflection

Automation moves tickets — assignee, tags, priority, status — and never writes customer-facing text. The human gates stay untouched on top of that:

* **[Reply approvals](/inbox/reply-approvals)** still gate every composed reply: a rule that reassigns a ticket to a gated agent changes who drafts the answer, never whether it needs review before it goes out.
* **AI deflection** runs before the ticket exists. When a visitor's question is resolved by the assistant, no ticket opens and automation has nothing to fire on; when deflection hands off, the ticket arrives through the normal create path and your `ticket_created` rules apply exactly as they would to an agent-filed ticket.

The practical rule: automation decides *which ticket and whose desk*, the approval gate decides *what the customer reads*. Keep automation out of the reply path and the two never fight each other.

## See also

* [SLA escalation policies](/inbox/sla-escalation-policies) — per-digital-queue breach thresholds, cooldowns, and escalation ladders that compose with ticket-level rules.
* [Reply approvals](/inbox/reply-approvals) — the supervisor review gate on outgoing replies.
* [Inbox SLA timers](/guides/inbox-sla-timers) — conversation-level first-response and resolution clocks whose due windows a priority-changing rule re-scales.
* [Webhook events](/webhooks/events) — subscribe to the `ticket.automation_applied` event stream your notify actions ride on.
