# Create a new business rule

`POST /api/business-rules`

- **Products:** Operator (`FOH`)
- **Features:** Front of House Generic Access (`FOHGENERIC`)

HTML version: [Create a new business rule](https://support.gladstonesoftware.cloud/reference/bookings/create-a-new-business-rule)

## Overview

Creates a business rule: a piece of automation that watches for an event on the system — the
**trigger** — and, when that event happens for a member, sends one or more emails or calls one or
more webhooks.

A rule is a trigger plus a list of **actions**. When the trigger fires, every active rule for that
trigger is evaluated. Within a rule, the conditional actions are tried in ascending `priority`
order and the first one whose `conditions` match the member is selected; if none match, the single
**default** action is selected instead. Exactly one action per rule runs, and its `results` are
what actually happen.

That structure is enforced, so the smallest valid rule is one action with `isDefault` set to
`true`, `priority` set to `0`, no `conditions`, and at least one entry in `results`. Every extra
action must be conditional: `isDefault` `false`, a unique `priority` above zero, and at least one
condition.

On success the endpoint responds `201 Created` with the new rule's id in the body — pass it to
[Retrieve details for a business rule](retrieve-details-for-a-business-rule.md)
or [Delete a business rule](delete-a-business-rule.md). Rules overlap freely: if
two rules match the same trigger and member, both run, and the member receives both sets of
emails.

## Endpoint

`POST {base_url}/api/business-rules`

`{base_url}` is the environment URL issued to your integration. See [Authenticating with Gladstone APIs](../../guides/authenticating-with-gladstone-apis.md).

## Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `description` | string | Yes | What the rule is for, in your own words. Stored on the rule and shown wherever rules are listed; it has no effect on behaviour. Omitting it, or sending an empty string, fails with `Description must be specified`. |
| `isActive` | boolean | No | Whether the rule is evaluated when its trigger fires. Only active rules are picked up, so `false` creates the rule dormant — useful for staging a rule before switching it on. Defaults to `true` when omitted or sent as `null`. |
| `trigger` | object | No | The event that causes this rule to be evaluated. Send it even though it is not marked as required: a request with no `trigger` fails with a `500`. Only `id` is used — see below. |
| `trigger.id` | integer (int32) | No | Identifies which event the rule listens for, and is the only part of `trigger` that is used. Must be `1` or greater, or the request fails with `Trigger '{id}' must be a positive number.`, and must match a trigger configured on your system, or `Trigger '{id}' doesn't exist.` The trigger determines which events reach the rule at all; a rule cannot listen for more than one. |
| `trigger.name` | string | No | Ignored on create. The name belongs to the trigger, not to your rule, so whatever you send here is discarded; the real name is returned by [Retrieve details for a business rule](retrieve-details-for-a-business-rule.md). |
| `trigger.description` | string | No | Ignored on create, exactly as `trigger.name` — the value returned comes from the trigger itself, never from your request. |
| `actions` | array of object | No | The branches of the rule; each pairs a set of `conditions` with the `results` to carry out when they match. Exactly one entry must be the default (`isDefault` `true`); the others are tried in `priority` order and the first match wins, so at most one action ever runs per rule. Send it even though it is not marked as required: a request with no `actions` fails with a `500`, and an empty array fails with `Exactly one default action must be specified.` |
| `actions[].description` | string | Yes | What this branch of the rule is for. Required on every action — omitting it fails with `Description must be specified`, the same message as the rule-level `description`, so check both when you see it. |
| `actions[].priority` | integer (int32) | No | The order in which conditional actions are tried, lowest first; the first action whose conditions match the member wins and the rest are skipped. Must be `0` on the default action (`Default actions must have a priority of zero.`) and greater than `0` on every conditional one (`Non-default actions must have a priority greater than zero.`). Negative values fail with `Priorities must be a positive integer.`, and no two actions in the same rule may share a value (`Actions can not have duplicate priorities.`). |
| `actions[].isDefault` | boolean | No | Marks this action as the fallback that runs when no conditional action matches the member. Exactly one action must set it — no more, no fewer, or the request fails with `Exactly one default action must be specified.` Defaults to `false` when omitted, so a rule with a single action must set it to `true` explicitly. A default action must have no `conditions` (`Default actions cannot have conditions.`) and a `priority` of `0`. |
| `actions[].conditions` | array of object | No | The tests the member must satisfy for this action to be selected. They are combined with `AND` and tested against the member's record, so every condition must hold. Required on conditional actions — an action with `isDefault` `false` and no conditions fails with `Non-default actions must have at least one condition.` — and forbidden on the default action, which fails with `Default actions cannot have conditions.` |
| `actions[].conditions[].fieldName` | string | No | The member field being tested, paired with `tableName`. The pair must resolve to a condition field configured on your system, or the request fails with `Condition fieldName '{tableName}.{fieldName}' doesn't exist.` — the same message whichever half is wrong. Only fields on that whitelist can be used; you cannot test an arbitrary field. |
| `actions[].conditions[].tableName` | string | No | The table the field in `fieldName` belongs to. The two are validated together and only as a pair; see `fieldName` for the failure message and the whitelist. |
| `actions[].conditions[].conditional` | string | No | The comparison operator. Must be one of `=`, `<>`, `>`, `<`, `>=`, `<=`, `IN`, `NOT IN`, `LIKE`, `NOT LIKE`, matched case-insensitively; anything else fails with `Condition '{conditional}' is not valid, must be one of =,<>,>,<,>=,<=,IN,NOT IN,LIKE,NOT LIKE`. Send it whenever you send a condition — omitting it inside a condition fails with a `500`. |
| `actions[].conditions[].value` | string | Yes | The value `fieldName` is compared against, always sent as a string regardless of the underlying field type. Required on every condition — omitting it or sending an empty string fails with `Value must be specified`. |
| `actions[].results` | array of object | No | What actually happens when this action is selected: the emails to send and the webhooks to call. Every action needs at least one, including the default; an action with an empty or missing `results` fails with `All actions must have one or more results.` Several results on one action all run. |
| `actions[].results[].type` | string | No | Which kind of result this is, matched case-insensitively against the action types configured on your system; an unrecognised value fails with `Action Result Type '{type}' doesn't exist.` Two names are treated specially: `email`, which requires `templates` and forbids `webhooks`, and `webhook`, which requires `webhooks` and forbids `templates`. |
| `actions[].results[].templates` | array of integer | No | The ids of the email templates to send. Required, and non-empty, when `type` is `email` — otherwise `Action Result Type '{type}' requires one or more email templates to be specified.` — and every id must already exist, or `One or more email template IDs do not exist`. Must be omitted or empty for any other `type`, which fails with `Email templates cannot be specified for Action Result Type '{type}'.` Listing several sends all of them. |
| `actions[].results[].webhooks` | array of integer | No | The ids of the webhooks to call, mirroring `templates`. Required, and non-empty, when `type` is `webhook` — otherwise `Action Result Type '{type}' requires one or more webhooks to be specified.` — and every id must already exist, or `One or more webhook IDs do not exist`. Must be omitted or empty for any other `type`, which fails with `Webhooks cannot be specified for Action Result Type '{type}'.` |
| `actions[].results[].description` | string | No | Ignored on create. The description you see when reading the rule back is the action type's own description, not anything you send here. |

## Sample request

```bash title="cURL"
curl -X POST '{base_url}/api/business-rules' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "description": "string",
  "isActive": true,
  "trigger": {
    "id": 12345,
    "name": "string",
    "description": "string"
  },
  "actions": [
    {
      "description": "string",
      "priority": 1,
      "isDefault": true,
      "conditions": [
        {
          "fieldName": "string",
          "tableName": "string",
          "conditional": "string",
          "value": "string"
        }
      ],
      "results": [
        {
          "type": "string",
          "templates": [
            1
          ],
          "webhooks": [
            1
          ],
          "description": "string"
        }
      ]
    }
  ]
}'
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | The action structure is wrong: `Exactly one default action must be specified.`, `Default actions cannot have conditions.`, `Non-default actions must have at least one condition.`, `All actions must have one or more results.` |
| `400` | Bad request | A priority is invalid: `Default actions must have a priority of zero.`, `Non-default actions must have a priority greater than zero.`, `Priorities must be a positive integer.`, `Actions can not have duplicate priorities.` |
| `400` | Bad request | A required value is missing: `Description must be specified` (on the rule or on an action), `Value must be specified` (on a condition). |
| `400` | Bad request | The trigger is unusable: `Trigger '{id}' must be a positive number.` or `Trigger '{id}' doesn't exist.` |
| `400` | Bad request | A condition does not resolve: `Condition fieldName '{tableName}.{fieldName}' doesn't exist.` or `Condition '{conditional}' is not valid, must be one of =,<>,>,<,>=,<=,IN,NOT IN,LIKE,NOT LIKE`. |
| `400` | Bad request | A result does not match its type: `Action Result Type '{type}' doesn't exist.`, `Action Result Type '{type}' requires one or more email templates to be specified.`, `Email templates cannot be specified for Action Result Type '{type}'.`, and the equivalent pair for webhooks. |
| `400` | Bad request | A referenced record is missing: `One or more email template IDs do not exist` or `One or more webhook IDs do not exist`. |
| `404` | Not found | This endpoint does not return `404` — a trigger, template or webhook that does not exist comes back as `400`, not `404`. |
| `500` | Internal server error | An unexpected server-side failure, with an empty body. Omitting `trigger` or `actions` altogether, or omitting `conditional` inside a condition, also returns `500` rather than `400`. |
