Idempotency
Networks drop responses, CI runners retry, automation re-applies. An idempotency key makes those retries safe: send the same key with the same request and managed.dev returns the original result — the job or resource you already created — instead of creating a second one.
The Idempotency-Key header
Section titled “The Idempotency-Key header”Send an Idempotency-Key header on any POST that creates a resource or kicks off a
job. The value is a unique string you choose, up to 255
characters — a UUID is the natural choice:
POST /v1/sites/site_01J7…/environments HTTP/1.1Host: api.managed.devAuthorization: Bearer mfk_live_…Forge-Version: 2026-06-23Idempotency-Key: 6f1c2e9a-4b7d-4f0a-9c11-2a8e5d3b1f04Content-Type: application/json
{ "type": "branch", "branch": "feature/checkout" }The platform stores the key and the result it produced for 24 hours. Inside that window, a replay returns the original response verbatim, plus a marker header so you can tell an echo from a fresh result:
HTTP/1.1 202 AcceptedIdempotent-Replayed: trueAfter the window expires, the same key is a fresh request again.
Replay is an echo, not a re-trigger
Section titled “Replay is an echo, not a re-trigger”This is the rule that makes retries safe: a replay echoes the original outcome — it does not re-run the work.
- First request with key
6f1c…→ the plugin-install job is created and returned (202 Accepted). - Any retry with the same key
6f1c…within 24h → the samejob_01J9…is returned, with its current status. The install is not started a second time. - This holds even if the original job has since failed. You get the original, failed job back — not a fresh attempt. To genuinely retry the operation, issue a new request with a new key.
Why it matters for CI
Section titled “Why it matters for CI”Automated callers retry aggressively, and a retry that double-creates is a real hazard: two preview environments, a duplicated deployment, a second backup.
- CI pipelines retry on transient network failures. Generate one key per logical step so a retried step reconciles against the first attempt instead of stacking a second.
- The Go SDK generates an
Idempotency-Keyautomatically on everyPOST, so its retries are safe out of the box. Opt out withforge.WithoutAutoIdempotency()at client construction, or pin your own key per request withctx = forge.WithIdempotencyKey(ctx, key). - The Terraform provider
Preview is designed around the same model — an
interrupted
applyre-finds the original job rather than provisioning a duplicate.
What is and isn’t safe to retry
Section titled “What is and isn’t safe to retry”| Situation | Retry safely? |
|---|---|
Any request, with the same Idempotency-Key, within 24h |
Yes — you get the original result back. |
A GET (or any read) |
Yes — reads have no side effects, key or not. |
A POST without a key |
No — there’s nothing to deduplicate against; you may create twice. |
| The same key after 24h | No — the record has expired; it’s treated as new. |
| The same key with a changed body | No — that’s a conflict, not a retry. Use a new key. |
The takeaway: put an Idempotency-Key on every creating POST, even the ones you
don’t expect to retry. It costs nothing on the happy path and turns an ambiguous
network failure into a safe, deterministic replay.
A worked example
Section titled “A worked example”curl -X POST https://api.managed.dev/v1/sites/site_01J7…/environments \ -H "Authorization: Bearer mfk_live_…" \ -H "Forge-Version: 2026-06-23" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "type": "branch", "branch": "feature/checkout" }'key := uuid.NewString()ctx = forge.WithIdempotencyKey(ctx, key) // resend the same key on any retry
job, err := client.Environments.Create(ctx, "site_01J7…", forge.EnvironmentCreateParams{ Type: forge.EnvTypeBranch, Branch: "feature/checkout", },)The first call returns 202 Accepted with a job; any replay inside 24h returns that
same job. Tail it to completion as described in
async jobs.