Hodoflow has two distinct duplicate-suppression contracts and ordinary retry guidance. Do not reuse one surface's behavior as a promise for another.
JSON:API unsafe writes
Send a unique Idempotency-Key on POST, PUT, PATCH, or DELETE requests
under /api/json. The key is scoped by tenant and service-account principal.
The request fingerprint includes method, path, and raw body.
Idempotency-Key: dashboard-create-20260731-001
- First request: reserves the key and runs normally.
- Same key and same request: replays the stored response with
idempotency-replayed: true. - Same key and different request: returns
409 idempotency_key_reused. - Concurrent duplicate while the first is running: returns
409 idempotency_key_in_progress. - A
5xxresponse is not retained, so a transient failure remains retryable. - If the key cannot be claimed at all, the request is refused with
503 idempotency_unavailableand nothing is written. Retry with the same key.
A reservation left behind by a request that never finished is released after five minutes, so an interrupted call cannot strand its key for the full retention window.
Completed responses are retained for 24 hours by default; deployments can configure the window. Generate keys from a stable business operation ID, not a random value on every retry. Never put a credential or tenant secret in a key.
Webhook delivery deduplication
Webhook ingestion uses a stable sender delivery identifier from
Idempotency-Key or a supported provider header. It retains accepted delivery
identity for seven days. A duplicate receives 202 with
{"status":"duplicate"} and no new run ID.
When no stable header exists, Hodoflow falls back to a time-bucketed hash of the trigger token and raw body. That protects short retries but is not a substitute for a provider delivery ID. See Retries and troubleshooting.
Retry matrix
| Result | Retry? | Action |
|---|---|---|
| Network timeout before any response | Yes | Retry with the same idempotency/delivery key |
429 |
Yes | Wait the Retry-After duration, then retry |
500 or 503 |
Usually | Exponential backoff with jitter and a bounded attempt/time budget |
401 |
Once after exchange | Discard cached token, exchange, retry once; then alert |
403 |
No | Correct scopes or run-as permissions |
404 |
No unchanged | Correct path/ID or object access |
409 idempotency conflict |
No | Reconcile key reuse; do not generate a new key blindly |
413 or 422 |
No unchanged | Correct body size or validation failure |
Reads are naturally repeatable, but still need bounded backoff. GraphQL
factQuery is read-only and does not use JSON:API response replay. A GraphQL
validation or complexity error is permanent until the document or variables
change.
Operational rules
- Cap attempts and total elapsed time.
- Add jitter so many workers do not retry together.
- Respect the credential-wide rate budget.
- Preserve method, path, body bytes, and idempotency key across retries.
- Record
x-request-id, status, attempt, and non-secret operation identity. - Never log bearer tokens, client secrets, trigger URLs, or sensitive bodies.
Practice the contract in Perform an idempotent write.