# Escalating expiration Use `ladder()` in **Expires in**, on the [Add to list](/en/rules/actions/add_to_list/) action, to keep a value on the list longer with each repeat offence. For example, an IP blocked for the first time leaves the list after 1 hour, and a repeat-offending IP stays for 2 days. ## Syntax ``` ladder(, , , ...) ``` - **Index**: a field (`$attempts`) or a reading published by an earlier action (`%blocks.cells.ip.value`), with a numeric value. A fixed number, such as `ladder(1, 1h, 4h)`, is rejected when the rule is saved. - **Expirations**: from 1 to 10 expirations, each a whole number followed by `m`, `h` or `d`. Each expiration is between `5m` and 2 years. The index picks the expiration by position, counted from 0: | Index | Expiration picked | | :----------------------- | :----------------------------------------- | | `0` | the first | | `1`, `2`, ... | the expiration at that position | | past the last expiration | the last | | decimal | rounded down: `2.7` picks position 2 | | missing or negative | the first | With `ladder(%blocks.cells.ip.value, 1h, 4h, 16h)`, index 0 picks 1 hour, index 1 picks 4 hours and index 2 or higher picks 16 hours. ## Blocking repeat offenders The index is usually the count of earlier blocks of the same value. The `blocks` analysis has a [no-trigger cell](/en/rules/analyses/#no-trigger) `ip`, which counts events by `$ip` over 30 days. The rule records an event in that analysis only when the IP actually enters the list: ``` Condition 1: "ip_velocity" in %risk.triggered_cells Actions: Run analysis: blocks (result: blocks) Add to list → $ip to @blocked_ips, expires in ladder(%blocks.cells.ip.value, 1h, 4h, 16h, 2d) (result: ip_blocked) Condition 2: %ip_blocked.added == true Actions: Record analysis: blocks ``` The action's **Result** field publishes `%ip_blocked.added`. The reading is `true` only when the run put the IP on the list. While the IP is on the list, the following events read `false` and do not count as a new block. In a burst of events from the same IP, only one run reads `true`. The `ip` cell thus counts the IP's blocks, not each suspicious event: | Block | Count in the `ip` cell | Expiration on the list | | :--------------- | :--------------------- | :--------------------- | | first | 0 | 1 hour | | second | 1 | 4 hours | | third | 2 | 16 hours | | fourth onwards | 3 or more | 2 days | The cell's window defines how long a block counts as a repeat offence. In the example, an IP with no blocks in 30 days goes back to the 1-hour expiration. ## Points to consider - **The expiration never shortens**: if the value is already on the list, the run keeps the longer of the current expiration and the one picked, as described in [Add to list](/en/rules/actions/add_to_list/). - **Observation and simulation do not write to the list**: in [observation](/en/rules/versioning/#observation) and in [simulation](/en/rules/simulation/), the action does not add the value and does not publish the result.