# Writes: reason, simulation and version

> How SDK write methods take a change reason, simulate with dryRun and confirm against the zone version with ifMatch.

Write methods take three change options next to their parameters, the same protocol described in [Simulate, then confirm](https://developers.openwaf.dev/change-control/simulate-and-confirm/):

| Option | TypeScript | Python | Sent as |
|---|---|---|---|
| Reason | `reason` | `reason=` | `X-Change-Reason`. Required on configuration writes (the type makes it mandatory), 1–500 characters. |
| Simulation | `dryRun: true` | `dry_run=True` | `?dry_run=1`: the answer is the plan, nothing changes. |
| Expected version | `ifMatch` | `if_match=` | `If-Match: "<version>"`. A stale version fails with `412 version_conflict`. Required on `revertZone` / `revert_zone`. |

A method only accepts the options its operation supports: for example `purgeCache` takes a reason and `dryRun` but no `ifMatch`, because purges are not versioned.

## The pattern

1. Call the method with `dryRun`. The answer is an `Applied` plan: `from_version`, `to_version`, `diff.changes` and `warnings`.
2. Show or check the diff.
3. Call the same method with the same arguments, without `dryRun`, and with `ifMatch` set to the plan's `from_version`.


**TypeScript**

```ts
import { OpenWAF, type Applied } from "./openwaf.ts";

const owf = new OpenWAF();
const params = { zone: zoneId, body: { hsts: { max_age: 300 } } };
const reason = "Enable HSTS";

const plan = (await owf.updateZoneEdge(params, { reason, dryRun: true })) as Applied;
console.log(plan.diff?.changes);
await owf.updateZoneEdge(params, { reason, ifMatch: plan.from_version! });
```
A method that can answer either a plan or a result is typed as a union, such as `Promise<ZoneEdgeSettings | Applied>`; cast the simulation's answer to `Applied`.
**Python**

```py
from openwaf import OpenWAF

owf = OpenWAF()
patch = {"hsts": {"max_age": 300}}
reason = "Enable HSTS"

plan = owf.update_zone_edge(zone=zone_id, body=patch, reason=reason, dry_run=True)
print(plan["diff"]["changes"])
owf.update_zone_edge(zone=zone_id, body=patch, reason=reason, if_match=plan["from_version"])
```


If the zone changed between the two calls, the second one raises `version_conflict` and applies nothing: see [Error handling](https://developers.openwaf.dev/sdk/error-handling/#version-conflict).

## The new version

The SDK returns the answer's body, not its headers, so the new version number (`X-Config-Version`) is not part of the result for most writes. Read it when you need it: `getZone` / `get_zone` answers the zone with its `config_version`, and the zone history lists every version. A revert's answer does carry `version` in its body.
