# Idempotency keys and dry runs

> Make retries safe with idempotencyKey, and preview any change with dryRun before it happens.

Source: https://docs.xholic.ai/api/idempotency/

## Idempotency keys

Networks fail and agents retry. An idempotency key makes sure a retried change happens once.

Pass `idempotencyKey` (8 to 200 characters, unique per change) on any action that changes something:

```json
{
  "text": "We just shipped dark mode.",
  "idempotencyKey": "post-2026-11-03-dark-mode"
}
```

- **Same key, same input:** you get the first call's result back, and nothing runs again.
- **Same key, different input:** `IDEMPOTENCY_KEY_REUSED`. Use a new key for a new change.
- **Same key while the first call is still running:** `IDEMPOTENCY_IN_PROGRESS` with `retryAfterSeconds`. Retry with the same key.
- If the call fails, the key is released, so retrying with it runs the action again.

`publish_now` (`posts.publish_now`) **requires** a key, so a post can never go out twice. For everything else it's optional but recommended in scripts and agents. Reads ignore it.

Good keys come from your own data: a row id, a hash of the content, or a UUID you store with the job.

## Dry runs

Pass `dryRun: true` on any change to see exactly what would happen, with nothing changed and no credits spent:

```json
{
  "ok": true,
  "summary": "Dry run: Schedule a post would run on @yourhandle with this input. Nothing was changed.",
  "data": {
    "capabilityId": "posts.schedule",
    "account": "yourhandle",
    "input": { "text": "…", "scheduledAt": "2026-11-03T09:00", "timezone": "Europe/London" }
  },
  "dryRun": true
}
```

A dry run still checks your credential, scope, account, plan and input, so it's a cheap way to test a request before running it for real. It doesn't check for conflicts that only appear when the change runs (for example a time slot taken in the meantime).

In the CLI, `--dry-run` does the same.
