---
title: "Dry-run an automation rule"
method: POST
path: "/companies/{company}/automations/rules/{rule}/dry-run"
tags: ["Automations"]
---

# Dry-run an automation rule

`POST /companies/{company}/automations/rules/{rule}/dry-run`

## Request

- Base URL: `https://api.factuarea.com/v1`
- URL: `https://api.factuarea.com/v1/companies/{company}/automations/rules/{rule}/dry-run`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Simulate what a rule would do against a sample event: condition match and what each action would resolve to. Executes nothing.
- No email, change, quota use or run record; it takes the read scope.
- For document rules pass `event_aggregate_id` of a real document, or no step would run.
- An unevaluable condition returns 200 with `condition_error`.

## Path parameters

- `company` string, uuid, required
- `rule` string, uuid, required

## Headers

- `Factuarea-Version` string, date

## Request body

- DryRunAutomationRuleV1Request
  - `event_type` string, required: Sample event type to test the rule with, in `resource.action` form (for example `invoice.paid`). Must belong to the catalog visible to the company.
  - `event_payload` object, required: The `data.object` of the sample event: an object with named keys, never a list.
  - `event_aggregate_id` string, uuid, nullable: ID (UUID v7) of the sample resource to predict against. Required for rules whose actions operate on a document.

## Response `200`

- object
  - `data` AutomationDryRun, required: What an automation rule WOULD do against a sample event, without materialising a single effect: no e-mail is sent, no webhook delivery is created, no target entity is mutated, no run or step row is written, and neither the monthly budget nor the rate limit is consumed. That is why the operation only requires the read scope even though it is a POST.
    - `object` 'automation_dry_run', required: Always `automation_dry_run`.
    - `automation_rule_id` string, uuid, required: UUID v7 of the automation rule that was tested.
    - `trigger_type` string, required: Trigger declared by the rule, in `resource.action` form. It is the only event type the rule can be tested against.
    - `matched` boolean, required: Whether the condition of the rule would be satisfied by the sample payload. When `false`, no step would fire.
    - `condition_trace` object[], required: One entry per LEAF node that was evaluated, in evaluation order — which is the order the condition was written in. An empty array with `matched: true` means the rule has no condition and always acts. Same shape the real execution path produces.
      - `path` string, required: Path of the evaluated field inside the `data.object` of the sample event.
      - `operator` 'eq' | 'neq' | 'in' | 'not_in' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'is_null', required: Operator that was applied.
      - `expected` unknown, required
      - `observed` unknown, required
      - `matched` boolean, required: Whether this leaf was satisfied.
    - `condition_error` string, nullable, required: Why the condition could not be evaluated, in Spanish, or `null` when it evaluated fine. It travels in the DOCUMENT with a 200, not as an HTTP failure: seeing why a rule cannot be evaluated BEFORE activating it is exactly what the dry run is for. Not redundant with `matched`: a non-null error implies `matched: false`, but the common case is a condition that evaluates perfectly and simply is not satisfied.
    - `steps` object[], required: One entry per action, in the order they would run.
      - `index` integer, required: Position the step WOULD have in the real run — the one that results from ordering the actions by their `order`, not the position in the stored array. It matches the `step_index` you would later replay.
      - `action_type` string, required: Raw value of the action type, exactly as stored in the definition. Returned verbatim and NOT as a closed enum: a type retired from the catalogue must still be countable in a dry run instead of breaking it.
      - `executable` boolean, required: Whether the code path reached the edge of the effect without anything discarding it. It never promises the effect would SUCCEED. It is `false` in three cases: the condition of the rule is not satisfied, the action type has no registered adapter, or the adapter refused the action because of some state of the world.
      - `resolved_parameters` object, required: Parameters that would be frozen into the step — those of the current rule definition. `{}` when the action takes none.
      - `effects` object[], required: Traces of the effects that would fire, in order. Same shape the real execution path records.
        - `kind` string, required: Family of the effect (sending an e-mail, mutating a document, …).
        - `target` string, required: What the effect would act on.
        - `summary` object, required: What the adapter derived from the parameters — recipient, subject, document — which is where the detail of the effect is readable. `{}` when there is none.
        - `simulated` boolean, required: Always `true` here: the trace comes from the simulator, never from a materialised effect.
      - `reason` string, nullable, required: Why the step would NOT run, in Spanish, or `null` when it would. A guardrail that would also cut the real execution shows up here as a non-executable step instead of faking success.

## Other responses

- `400`: `parameter_unknown` with `subcode` `retired_active_profile_header`: the retired `X-Active-Profile` header was sent; the company goes in the path. `parameter_invalid_format` on `Factuarea-Version`: the header is not a `YYYY-MM-DD` date. `unsupported_api_version`: the `Factuarea-Version` date is not a supported version.
- `401`: Missing or invalid API key.
- `403`: The request is authenticated but not allowed: the API key lacks the required scope (`insufficient_scope`), the plan does not include the public API or this feature (`addon_not_active`, `feature_not_available_in_plan`), or the module is not available with a test key (`module_not_available_in_sandbox`). `company_inactive`: the company in the path is deactivated; reactivate it to operate.
- `404`: The requested resource does not exist or belongs to another company. `profile_not_found`: the company in the path does not exist, is not in the API key scope or belongs to the other environment (live or test).
- `422`: Validation failed. The `error.param` field identifies which input is invalid. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; send a new key or the same body. `invalid_param_format` on `Idempotency-Key`: the key must be 1 to 255 printable ASCII characters.
- `429`: Rate limit exceeded. Retry after the duration in `Retry-After`.
- `500`: Unexpected server error.

---

[API](https://skmtc.dev/factuarea/apis/factuarea-public-api.md) · [All operations](https://skmtc.dev/factuarea/apis/factuarea-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/factuarea/apis/factuarea-public-api/revisions/718e77934422?raw)
