Version

Idempotency

Every POST, PATCH, PUT and DELETE takes an Idempotency-Key header so a retry never double-creates or double-mutates.

Every POST, PATCH, PUT and DELETE request against /v1/* requires an Idempotency-Key header. The key makes retries safe: replaying with the same key returns the cached response, while replaying the same key with a different body is rejected.

Header contract

POST /v1/webhook-endpoints HTTP/1.1
Authorization: Bearer zg_live_...
Idempotency-Key: 0199a4ae-6c02-7b5c-9f51-c5f0da5e2f12
Content-Type: application/json
  • An opaque string, required on every mutation.
  • Any format works; generate a UUID per call.
  • Omit the header and the API returns IDEMPOTENCY_KEY_MISSING (HTTP 400).

Replay semantics

ConditionServer responseReply header
First time this key is seenFresh response, normal HTTP status
Same key, same request body hashCached response, same HTTP statusIdempotent-Replay: true
Same key, different request body hash409 IDEMPOTENCY_KEY_REUSED
Same key, concurrent in-flight425 IDEMPOTENCY_REPLAY_IN_PROGRESS

Successful 2xx JSON responses are cached under a hash of the token, method, path and key for 24 hours, with the response body encrypted at rest, so the one-time secrets in a token mint or a webhook endpoint registration never sit in the cache in clear text. After that, the same key rotates out of the cache and behaves as a fresh request. 4xx and 5xx responses are not cached; callers are free to retry them.

Request body fingerprint

Reuse detection hashes the full raw request body. Two requests are "the same" when their token, method, path, key and body all match byte for byte, which keeps accidental reuse obvious.

Client-side recipe

A safe retry loop:

$key = (string) Str::uuid();

$response = Http::withHeaders([
    'Authorization' => "Bearer {$token}",
    'Idempotency-Key' => $key,
    'Content-Type' => 'application/json',
])->retry(
    times: 3,
    sleepMilliseconds: 500,
    when: fn ($exception, $request) => $exception?->response?->status() >= 500,
)->post($apiUrl.'/v1/webhook-endpoints', [
    'name' => 'Deploy hook',
    'url' => 'https://hooks.example.test/zusegenius',
    'events' => ['project.created'],
]);

If the first attempt succeeds and the retry fires anyway (your client timed out on a 200 it never saw), the second call hits the cache and returns the same response; you never double-register the endpoint.

Cached replays carry Idempotent-Replay: true so your integration can recognise them and skip any client-side side effects that might otherwise fire twice.

What does not require a key

  • GET requests, which are naturally idempotent.
  • /v1/ping.
  • Webhook deliveries from Zuse Bifrost to your endpoint, which carry their own event id for consumer-side de-duplication.

How is this guide?

Last updated on

Version

On this page

What changed recentlyEvery release of Zuse Bifrost, with the pages it touched.

For AI agents: llms.txt ยท llms-full.txt