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

# Rules: three shapes, not two

> The rules engine's three field shapes — action-only, DSL expression pair, and legacy structured criteria — and a worked DSL example.

## Rules: three shapes, not two

`/api/rest/rules` is a general-purpose object for account-specific if-this-then-that policies (see [Rule](/concepts/glossary#objects) in the glossary), but the fields you populate depend on the rule's `type` — mixing them up produces a rule that silently never fires.

* **Action-only** — no condition fields at all: only `actionType` + `actionValue` are set, and the rule's `type` itself determines when it applies. This is the most common shape in production (e.g. `ON_CREATION_FROM_EMAIL` rules) — don't assume every rule needs a condition or a DSL expression.
* **DSL expression pair** — used by `type: "CUSTOMER_INVOICE_EXCEPTION"` rules: `conditionExpression` (when the rule applies — an expression like `Shipment.mode = 'FTL' AND Invoice.totalAmount > 1000`, or the literals `ALWAYS` / `NEVER`) and `validationExpression` (what to check — `REQUIRE Document WHERE ...` or `VALIDATE ... = ...`, see the worked example below). **For `CUSTOMER_INVOICE_EXCEPTION` rules, send both DSL fields.** A check constraint (`check_rule_dsl_format`) enforces it for DSL-driven rules. The exception is **config-managed** exception types — ones evaluated in application code rather than by the DSL, which are allowed to carry NULL expressions and act as on/off toggles. That exempt set has been widened several times, so treat it as a moving list rather than a fixed one: if you are building a config-managed toggle row, check the current constraint instead of assuming a value is or isn't exempt. For anything DSL-driven, both fields are required.
* **Structured criteria** (`criteriaObject` / `criteriaField` / `criteriaOperator` / `criteriaValue`) exist on the table but have **no live usage today** — treat this as legacy/reserved rather than a mechanism to build against.

Other fields that apply regardless of mechanism:

| Field        | Notes                                                                                                                                                                               |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerId` | Scopes the rule to one customer. Omit (`null`) for a tenant-wide rule.                                                                                                              |
| `priority`   | Integer, defaults to `0`. Used to order evaluation when more than one rule could apply.                                                                                             |
| `enabled`    | Boolean, defaults to `true`. **A real, API-settable toggle** — set it to `false` to deactivate a rule without deleting it; this is the recommended way to pause a rule temporarily. |

<Tip>
  Rules don't soft-delete (no `deletedAt`) — `DELETE /api/rest/rules/{id}` is permanent. If you might want the rule back later, `PUT` it with `enabled: false` instead of deleting it.
</Tip>

### Example: validate a customer invoice against its customer rate confirmation

Once customer rate confirmations are flowing in, a **shipper rule** (a `CUSTOMER_INVOICE_EXCEPTION`-type [rule](#rules-three-shapes-not-two) using the DSL expression pair) can hold a customer invoice for review when its amount doesn't match the customer's rate confirmation. The rule compares the parsed customer rate confirmation total against the shipment's customer total rate — both customer-side amounts, stored in cents:

```
REQUIRE Document
WHERE Document.type = "CUSTOMER_RATE_CONFIRMATION"
AND VALIDATE CustomerRateConfirmation.totalRate = Shipment.customerTotalRate
```

When the amounts differ, the invoice opens a `CUSTOMER_INVOICE_EXCEPTION` and stays in Needs Review instead of going out to the customer. The comparison only ever reads `CUSTOMER_RATE_CONFIRMATION` documents — it never compares against a `CARRIER_RATE_CONFIRMATION`.
