# Retries and dry runs Two tools make changes through the API safe to automate. Use them in scripts, and always in AI agents. - **`Idempotency-Key`** lets you retry a `POST` without creating the same thing twice. - **`?dry_run=1`** shows what a DNS change would do, and changes nothing. ## Retry safely with `Idempotency-Key` A request can time out after it has already been carried out. If your code then retries it, a second DNS record or monitor can be created. Send an `Idempotency-Key` header with every `POST` to prevent that: ```sh curl -X POST https://api.infranest.io/api/organizations/42/tags \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Idempotency-Key: 6f1c2a7e-3b0d-4f6e-9a51-0c2d8e7b4a19" \ -H "Content-Type: application/json" \ -d '{"name": "web", "color": "#336699"}' ``` - **Make a new key for every new request.** A UUID works well. Keys can be up to 255 visible ASCII characters. - **Send the same key only when you retry that exact request.** You get the first answer back, with the header `Idempotent-Replayed: true`, and nothing is done again. - **Same key, different request:** the API answers `422` [`idempotency_key_reused`](errors.md#idempotency_key_reused). - **Same key while the first request is still running:** the API answers `409` [`idempotency_in_progress`](errors.md#idempotency_in_progress). Wait a few seconds and send it again. - **Only successful answers are kept, for 24 hours.** If the first attempt was refused or failed, nothing was changed, so a retry with the same key is handled as new. - **Keys belong to one token.** Another token using the same key never sees your answers. - **A replay is only returned while your access is unchanged.** If your role, the token's scopes, the plan or the organization's status changed since the first request, the request runs again and gets today's answer. `PUT`, `PATCH` and `DELETE` don't need a key: doing them twice has the same result as doing them once. ## Look before you change: `?dry_run=1` Add `?dry_run=1` to a DNS write to see the change it would make. Nothing is saved, nothing is sent to the DNS provider, and nothing appears in the audit log. It works on: | Request | Path | |---|---| | Create a record | `POST /organizations/{organization}/dns-zones/{zone}/dns-records` | | Change a record | `PUT /organizations/{organization}/dns-zones/{zone}/dns-records/{record}` | | Delete a record | `DELETE /organizations/{organization}/dns-zones/{zone}/dns-records/{record}` | | Apply a template | `POST /organizations/{organization}/dns-templates/{template}/apply/{zone}` | ```sh curl -X PUT "https://api.infranest.io/api/organizations/42/dns-zones/7/dns-records/311?dry_run=1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Content-Type: application/json" \ -d '{"value": "192.0.2.20"}' ``` ```json { "dry_run": true, "zone": "example.com", "provider_push": "per_record", "changes": [ { "action": "update", "before": { "type": "A", "name": "www", "value": "192.0.2.10", "ttl": 300 }, "after": { "type": "A", "name": "www", "value": "192.0.2.20", "ttl": 300 } } ], "unchanged": 0, "skipped": [] } ``` - **`changes`** lists each record that would be created, updated or deleted. `before` is `null` for a create, and `after` is `null` for a delete. An empty list means there is nothing to do. - **`unchanged`** counts records that are already as requested. - **`skipped`** says why a template record would be left out, for example a value that does not fit its type. - **`provider_push`** says how the change would reach the DNS provider: - `per_record`: just these records; - `whole_zone`: the provider takes the whole zone at once; - `none`: the zone is only kept in InfraNest. - **Refusals are the same as for the real request.** A dry run that would be refused answers with that refusal, such as `409` for a record that already exists, or a missing scope. - **Sending the same request without `?dry_run=1` makes exactly that change**, as long as the zone has not changed in between. A dry run counts towards your [rate limit](tokens-and-scopes.md#rate-limits) as a change. ## For AI agents Dry-run every DNS change, show the result to the person you work for, and send the real request only after they agree. Send an `Idempotency-Key` with every `POST`, and keep the key when you retry.