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

# Verification Rules

> Configure what guests must complete before check-in.

A verification rule says "for stays that look like this, collect this before check-in." Each rule has a **step type**, a **config**, and optional **targeting**.

## Step types

| `step_type`            | Slot            | Collects                                    |
| ---------------------- | --------------- | ------------------------------------------- |
| `id_check`             | `identity`      | An identity document.                       |
| `agreement`            | `agreement`     | A signature on a document. Never priced.    |
| `damage_waiver`        | `signature`     | A signature on a waiver, optionally priced. |
| `security_deposit`     | `deposit`       | A refundable hold.                          |
| `mandatory_fee`        | `fee`           | A non-refundable charge.                    |
| `guest_details`        | `guest_details` | Email, phone, arrival and departure times.  |
| `additional_questions` | `questions`     | Free-form questions you define.             |
| `other_guests`         | `other_guests`  | Details for everyone else on the booking.   |

## One rule per slot

Rules are evaluated in `order`. For each slot, **the first enabled rule whose targeting matches the stay wins, and every later rule for that slot is skipped.**

This makes ordering meaningful. A specific rule must be above a general rule, or it never applies:

```
order 1  Deposit, stays of 7+ nights, $500     <- matches a 10-night stay, claims "deposit"
order 2  Deposit, every stay, $250             <- skipped for that stay, applies to the rest
```

If you reverse the order, the catch-all rule always claims the slot. The API does not return an error.

Rules compete only within a slot. `damage_waiver` and `agreement` use different slots, so both can apply.

## Creating a rule

Call [Create Verification Rule](/api-reference/portals/verification-rules/create) to add arrival details to every stay. Omit `targeting` to match every stay:

```json theme={null}
{
  "name": "Arrival details",
  "step_type": "guest_details",
  "config": {
    "title": "When are you arriving?",
    "collectEmail": true,
    "collectPhone": true,
    "collectEta": true,
    "collectEtd": false,
    "requiredFields": ["phone", "eta"]
  }
}
```

`requiredFields` must be a subset of the collected fields. The API rejects `eta` when `collectEta` is `false`.

The following rule adds a deposit for long stays:

```json theme={null}
{
  "name": "Deposit on long stays",
  "step_type": "security_deposit",
  "config": {
    "title": "Security deposit",
    "instructions": "Released 7 days after checkout.",
    "amountInCents": 50000,
    "currency": "usd",
    "releaseDelayDays": 7
  },
  "targeting": {
    "minNights": 7,
    "bookingChannelMode": "exclude",
    "bookingChannels": ["airbnb"]
  }
}
```

New rules are enabled and appended. Move a specific rule above a catch-all rule with [Reorder Verification Rules](/api-reference/portals/verification-rules/reorder).

<Accordion title="The other six step types">
  **Agreement.** A document to sign, with no charge attached. Pricing an agreement is rejected; use `damage_waiver` instead.

  ```json theme={null}
  {
    "step_type": "agreement",
    "config": {
      "title": "House rules",
      "body": "Quiet hours are 10 PM to 8 AM. No smoking anywhere on the property."
    }
  }
  ```

  **Damage waiver.** A signature that can carry a price. `model` is `flat` or `per_night`. `capInCents` limits a per-night total and must be at least `amountInCents`.

  ```json theme={null}
  {
    "step_type": "damage_waiver",
    "config": {
      "title": "Damage waiver",
      "body": "Covers accidental damage up to $1,000.",
      "pricing": {
        "model": "per_night",
        "amountInCents": 1200,
        "currency": "usd",
        "capInCents": 8400
      }
    }
  }
  ```

  **Mandatory fee.** Same shape as a deposit but non-refundable, so no `releaseDelayDays`.

  ```json theme={null}
  {
    "step_type": "mandatory_fee",
    "config": {
      "title": "Resort fee",
      "instructions": "Covers pool and gym access.",
      "amountInCents": 3500,
      "currency": "usd"
    }
  }
  ```

  **Additional questions.** Question `id` values must be unique within the rule. Select questions need at least two options. Other question types take no options.

  ```json theme={null}
  {
    "step_type": "additional_questions",
    "config": {
      "title": "A few questions",
      "questions": [
        {
          "id": "purpose",
          "label": "What brings you here?",
          "type": "single_select",
          "required": true,
          "options": ["Holiday", "Work", "Family"]
        },
        {
          "id": "plate",
          "label": "Vehicle plate, if you are driving",
          "type": "text",
          "required": false
        }
      ]
    }
  }
  ```

  **Other guests.** Set `minGuests`, `maxGuests`, or both. `maxGuests` is limited to 50. A rule supports a maximum of 20 fields. Each field needs a unique `id`.

  ```json theme={null}
  {
    "step_type": "other_guests",
    "config": {
      "title": "Who else is staying?",
      "maxGuests": 6,
      "fields": [
        { "id": "full_name", "label": "Full name", "type": "text", "required": true },
        { "id": "photo_id", "label": "Photo ID", "type": "image", "required": false }
      ]
    }
  }
  ```

  **ID check.** Collects an identity document. It requires `title` and `instructions`. It also supports `requireContactPhoto` and `autoApprove`.
</Accordion>

## Targeting

Every criterion you set uses AND logic. Empty or omitted `targeting` matches every stay.

| Field                                    | Effect                                                                            |
| ---------------------------------------- | --------------------------------------------------------------------------------- |
| `minNights` / `maxNights`                | Stay length bounds. `minNights` cannot exceed `maxNights`.                        |
| `bookingChannels` + `bookingChannelMode` | `include` or `exclude` the listed channels. A mode requires at least one channel. |
| `maxBookingLeadDays`                     | Match bookings made within this many days of arrival.                             |
| `matchUnknownBookingLeadTime`            | Also match stays with no known booking date. Requires `maxBookingLeadDays`.       |
| `listingIds` / `listingGroupIds`         | Match a listed property or one of its groups.                                     |

`exclude_returning_guests` drops the rule for guests who stayed before. This is useful for one-time identity checks.

## Reordering

[Reorder Verification Rules](/api-reference/portals/verification-rules/reorder) takes every rule in the workspace. Include each current `expected_updated_at` value:

```json theme={null}
{
  "items": [
    { "id": "vr_long_stay_deposit", "expected_updated_at": 1730000000000 },
    { "id": "vr_standard_deposit", "expected_updated_at": 1730000000000 },
    { "id": "vr_arrival_details", "expected_updated_at": 1730000000000 }
  ]
}
```

The API rejects partial lists because accepting one would renumber omitted rules.

## Editing and deleting

[Update Verification Rule](/api-reference/portals/verification-rules/update) cannot change `step_type`. The supplied `config` must match the existing step type.

Edit uploaded agreement and waiver documents in the app.

[Delete Verification Rule](/api-reference/portals/verification-rules/delete) is permanent. Existing stays keep collected data, but new stays do not receive the step.

<Note>
  Priced rules include `security_deposit`, `mandatory_fee`, and `damage_waiver` with a nonzero price. When a workspace has an active template, these rules require Stripe before you enable them. You can create them before the workspace has an active template.
</Note>
