---
title: "Preview Rule"
method: POST
path: "/admin/notifications/rules/preview"
tags: ["admin", "notification-rules"]
---

# Preview Rule

`POST /admin/notifications/rules/preview`

## Request

- Base URL: `https://api-docs.upsmith.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api-docs.upsmith.com/admin/notifications/rules/preview`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

The same preview, for a staff caller who has no organization of their own.

Same handler as the authenticated tier, so an operator drafting through the MCP sees what the
editor would have seen rather than a second implementation's answer.

## Query parameters

- `organization_id` string, required

## Headers

- `organization-id` string, nullable
- `timezone` string
- `x-user-id` string, nullable
- `x-actor-email` string, nullable
- `x-request-source` string, nullable
- `x-operation-id` string, nullable
- `x-commit-message` string, nullable
- `x-confirmation-id` string, nullable
- `x-chat-id` string, nullable

## Request body

- RulePreviewRequest: A draft, and one of the two ways to say which events to run it against. ``event_type`` previews against this organization's most recent real subjects of that type, rebuilt. ``events`` previews against events the caller supplies. Exactly one of them: neither leaves nothing to evaluate, and both would mean two answers to one question. ``limit`` and the length of ``events`` are bound by the same ``MAX_PREVIEW_EVENTS`` the library binds, declared here so the refusal names the field rather than arriving as a ``ValueError`` from two layers down.
  - `decision` object, required
  - `event_type` string, nullable
  - `events` RulePreviewEventInput[], nullable
    - `type` string, required
    - `occurred_at` string, date-time, required
    - `subject_id` string, required
    - `payload` object
  - `limit` integer

## Response `200`

Successful Response

- RulePreviewResponse: One preview run. ``event_source`` says which of the two ways the events were named, because it is what makes an empty ``unreadable`` readable: for ``supplied`` events there is nothing to be unreadable, while for ``recent`` ones an empty list means the rule read nothing the rebuild had lost.
  - `event_source` string, required
  - `previews` RulePreviewEntry[], required
    - `subject_id` string, required
    - `event_type` string, required
    - `applies` boolean, required
    - `outputs` RulePreviewOutputs, required: What the rule resolved to for one event. Empty is a result, not a failure.
      - `recipients` RecipientRef[]
        - `kind` 'user' | 'entity' | 'technician' | 'rotation', required: What a recipient reference names, and in whose id space. The three destinations ``notification.recipient.type`` has, restated so a rule document needs no ORM to be authored or validated, plus a rotation, which names a schedule rather than a row; ``tests/test_outputs.py`` holds the two lists together, as it does for the escalation reasons. Which id a reference carries follows from its kind, the way every notification settings route already reads one: ``user`` carries an ``auth.user.id``, ``entity`` and ``technician`` carry a ``notification.recipient.id``, neither having a user behind it, and ``rotation`` carries a rotation id (:func:`parse_rotation_id`). So every recipient a subscription names can be written as one of the first three, and the kind says what the reference can receive rather than hiding it behind a read. A reference is resolved to a person when the message is sent, never here -- which is what lets an escalation step reach whoever is on call at that moment rather than whoever was when the event happened.
        - `id` string, required
      - `channels` string[]
      - `timing` string, nullable
      - `escalation` EscalationStep[]
        - `delay_minutes` integer, required
        - `recipients` RecipientRef[], required
          - `kind` 'user' | 'entity' | 'technician' | 'rotation', required: What a recipient reference names, and in whose id space. The three destinations ``notification.recipient.type`` has, restated so a rule document needs no ORM to be authored or validated, plus a rotation, which names a schedule rather than a row; ``tests/test_outputs.py`` holds the two lists together, as it does for the escalation reasons. Which id a reference carries follows from its kind, the way every notification settings route already reads one: ``user`` carries an ``auth.user.id``, ``entity`` and ``technician`` carry a ``notification.recipient.id``, neither having a user behind it, and ``rotation`` carries a rotation id (:func:`parse_rotation_id`). So every recipient a subscription names can be written as one of the first three, and the kind says what the reference can receive rather than hiding it behind a read. A reference is resolved to a person when the message is sent, never here -- which is what lets an escalation step reach whoever is on call at that moment rather than whoever was when the event happened.
          - `id` string, required
      - `immediate_recipients` RecipientRef[]
        - `kind` 'user' | 'entity' | 'technician' | 'rotation', required: What a recipient reference names, and in whose id space. The three destinations ``notification.recipient.type`` has, restated so a rule document needs no ORM to be authored or validated, plus a rotation, which names a schedule rather than a row; ``tests/test_outputs.py`` holds the two lists together, as it does for the escalation reasons. Which id a reference carries follows from its kind, the way every notification settings route already reads one: ``user`` carries an ``auth.user.id``, ``entity`` and ``technician`` carry a ``notification.recipient.id``, neither having a user behind it, and ``rotation`` carries a rotation id (:func:`parse_rotation_id`). So every recipient a subscription names can be written as one of the first three, and the kind says what the reference can receive rather than hiding it behind a read. A reference is resolved to a person when the message is sent, never here -- which is what lets an escalation step reach whoever is on call at that moment rather than whoever was when the event happened.
        - `id` string, required
    - `rotations` RulePreviewRotation[]
      - `rotation_id` string, required
      - `output` string, required
      - `escalation_step` integer, nullable
      - `at` string, date-time, required
      - `name` string, nullable
      - `technicians` RulePreviewTechnician[]
        - `technician_id` integer, required
        - `name` string, required
        - `channels` string[]
        - `reasons` string[]
      - `members` RulePreviewMember[]
        - `user_id` string, required
        - `name` string, required
        - `channels` string[]
        - `reasons` string[]
      - `refusal` string, nullable
    - `blocked` string, nullable, required
    - `unreadable` string[], required
    - `inconclusive` boolean, required
    - `trace` object, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/upsmith/apis/upsmith-api.md) - [All operations](https://skmtc.dev/upsmith/apis/upsmith-api/llms.txt) - [OpenAPI document](https://skmtc.dev/upsmith/apis/upsmith-api/revisions/86e2c2bd93af?raw)
