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

# Estimate and dry-run

> Size a Webhook Alerts rule before you create it: how often it would have fired per day, a ladder of thresholds, and the events it would have delivered.

A threshold cannot tell you how often it will fire. Two previews answer that before you create a rule, using the same detection and the same matching as delivery. Neither creates a rule, a delivery or anything else.

* **The estimate** replays a rule's configuration over the last 1 to 30 days and returns how many times a day it would have fired, its typical and busiest day, and a ladder of thresholds with the rate at each.
* **The dry-run** returns the individual occurrences a configuration would have delivered over the last 24 hours at most, so you can look at the matches themselves.

Both answer on every plan, Free included, for the event types they support. In the dashboard, the rule composer shows the estimate as **Estimated frequency** while you build the rule, with **Compare thresholds** for the ladder and **Preview matching events** for the dry-run. Over MCP they are `estimate_webhook_subscription` and `dry_run_webhook_subscription`.

## Which event types can be previewed

| Event type | Available previews |
| - | - |
| market.liquidation | Estimate and dry-run |
| account.fill | Estimate and dry-run |
| account.transfer | Estimate and dry-run |
| account.liquidated | Estimate only |
| market.liquidation\_burst | Estimate only |
| market.oi\_delta | Estimate only |
| market.funding\_flip | Estimate only |
| market.pga\_payment | Estimate only |
| hip4.settlement | Estimate only |
| oracle.jump | Estimate only |

**Needs a watched address:** `account.fill`, `account.transfer` and `account.liquidated`. Their previews scan your watched addresses, so they need at least one on your list, and Free cannot hold one. Every event type not in the table has neither preview, and a request for one returns a 400 that lists the supported types.

## Estimate

`POST /v1/webhooks/subscriptions/estimate`. The body is the create body minus `endpoint_id`: `event_type`, `config` (also accepted as `filters`), and `lookback_days` (1 to 30, default 7).

```bash theme={"theme":"github-dark"}
curl -X POST https://api.0xarchive.io/v1/webhooks/subscriptions/estimate \
  -H "X-API-Key: $OXARCHIVE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "event_type": "market.liquidation",
    "config": {"venue": "hyperliquid", "symbols": ["BTC"],
               "conditions": [{"metric": "notional_usd", "op": ">=", "value": 250000}]},
    "lookback_days": 7
  }'
```

```json theme={"theme":"github-dark"}
{
  "success": true,
  "data": {
    "event_type": "market.liquidation",
    "window": { "from": "2026-09-01T14:00:00.000Z", "to": "2026-09-08T14:00:00.000Z" },
    "days": 7,
    "total": 84,
    "per_day": [
      { "date": "2026-09-02", "count": 9 },
      { "date": "2026-09-03", "count": 31 },
      { "date": "2026-09-04", "count": 12 },
      { "date": "2026-09-05", "count": 8 },
      { "date": "2026-09-06", "count": 4 },
      { "date": "2026-09-07", "count": 6 },
      { "date": "2026-09-08", "count": 14 }
    ],
    "per_day_p50": 9.0,
    "per_day_max": 31,
    "primary_metric": "notional_usd",
    "ladder": [
      { "value": 50000, "per_day": 41.7 },
      { "value": 250000, "per_day": 12.0 },
      { "value": 1000000, "per_day": 2.1 }
    ],
    "distribution": { "n": 2140, "p50": 4200, "p90": 61000, "p99": 380000, "max": 2100000 },
    "sample": [ { "observed_at_estimate": "2026-09-08T13:41:01.586Z", "data": { "symbol": "BTC" } } ],
    "basis": { "mode": "exact", "note": null }
  }
}
```

* `window`, `days`: the window the answer covers. Some types are estimated over a shorter window than asked for: `account.fill` and `oracle.jump` over at most 7 days, `market.oi_delta` over at most 14.
* `per_day`: one entry per 24-hour period ending now, oldest first, labelled by the UTC date it ends on, with quiet days present as zero rather than missing.
* `per_day_p50`, `per_day_max`: the typical day and the busiest one. The busiest day matters more than the average when you are deciding whether to be paged.
* `primary_metric`, `ladder`: up to ten thresholds on the configuration's own threshold metric, each with the daily rate it would have produced, everything else unchanged. Empty for an event with no threshold metric, such as `market.funding_flip`.
* `distribution`: the metric itself over the window, which answers what counts as large on this market.
* `sample`: the newest matches, in the dry-run's shape.
* `basis.mode`: how the answer was reached. `exact` means every occurrence in the window was counted. `replayed` means the detector's own rules were re-run over history, which is how windowed events such as bursts are counted. `sampled` means a condition could not be expressed as a query, so a recent sample was scaled, and `basis.note` says so.

Percent thresholds are estimated against each market's reference values as they are now, not as they were at the time of each occurrence, which `basis.note` says when it applies.

## Dry-run

`POST /v1/webhooks/subscriptions/dry-run`. The body is the create body minus `endpoint_id`, plus the window and page size: `event_type`, `config` (also accepted as `filters`), `lookback_s` (60 to 86400, default 3600) and `limit` (1 to 200, default 100). Validation errors are identical to create.

```bash theme={"theme":"github-dark"}
curl -X POST https://api.0xarchive.io/v1/webhooks/subscriptions/dry-run \
  -H "X-API-Key: $OXARCHIVE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "event_type": "market.liquidation",
    "config": {"venue": "hyperliquid", "symbols": ["BTC"],
               "conditions": [{"metric": "notional_usd", "op": ">=", "value": 250000}]},
    "lookback_s": 21600,
    "limit": 5
  }'
```

```json theme={"theme":"github-dark"}
{
  "success": true,
  "data": {
    "event_type": "market.liquidation",
    "window": { "from": "2026-09-08T08:00:00.000Z", "to": "2026-09-08T14:00:00.000Z" },
    "matched": 12,
    "truncated": true,
    "occurrences": [
      {
        "observed_at_estimate": "2026-09-08T13:41:01.586Z",
        "data": {
          "venue": "hyperliquid",
          "symbol": "BTC",
          "timestamp": "2026-09-08T13:41:01.586Z",
          "account": "0x4378a374231ad915c6b93349521a1955808c8254",
          "side": "A",
          "direction": "Close Long",
          "notional_usd": 562419.19,
          "size": 7.23477,
          "vwap": 77738.36,
          "mark_price": 77739.0,
          "closed_pnl": -8218.95,
          "fill_count": 66,
          "min_trade_id": 6740275823509,
          "max_trade_id": 1121517228007121,
          "api_url": "/v1/hyperliquid/liquidations/BTC?start=1788874861586&end=1788874861587"
        }
      }
    ]
  }
}
```

`occurrences` are newest first, each with the `data` a delivery would carry and `observed_at_estimate`, the occurrence's own timestamp; a real delivery's `observed_at` is that plus the path's latency. `matched` counts every hit in `window`, before `limit`. `truncated` is `true` when `occurrences` is shorter than `matched`, or when a scan hit its row cap and `window.from` was moved forward so the window and the list agree.

Two deliberate differences from delivery: `max_age_s` is not applied, because you pick the window, and for `account.*` events the scan also covers occurrences from before the address was added, so you can see what a threshold would have caught. Spot fills are not included in an `account.fill` dry-run.

## Budgets and errors

The estimate and the dry-run share a budget of 6 calls a minute per account. They return market data, so they use API credits like other data requests; the routes that create and manage rules do not.

| Status | Meaning |
| - | - |
| 400 | The configuration fails validation, the event type has no estimate or dry-run, or an address preview has no watched address to scan. The message says which and what to do. |
| 429 | More than 6 estimates and dry-runs in a minute |
| 503 | The engine could not answer. Retry after the interval in the response's Retry-After header. |
| 504 | A dry-run ran out of time before the window was fully scanned. Try a shorter `lookback_s`. |

## Create the rule

When the rate is the one you want, create the rule with the same configuration and your endpoint. The create body is the estimate body with `endpoint_id` added and `lookback_days` removed:

```bash theme={"theme":"github-dark"}
curl -X POST https://api.0xarchive.io/v1/webhooks/subscriptions \
  -H "X-API-Key: $OXARCHIVE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "endpoint_id": "f4797c46-ada6-4c09-a076-5689a974e4be",
    "event_type": "market.liquidation",
    "filters": {"venue": "hyperliquid", "symbols": ["BTC"],
                "conditions": [{"metric": "notional_usd", "op": ">=", "value": 250000}]}
  }'
```

In the dashboard, choose **Add subscription** in the composer where you read the estimate. Over MCP, ask the agent to create the rule it just estimated. The new rule starts with `status` `active` and `enabled` `true`, and its deliveries appear in the endpoint's delivery log.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.