Idempotency
POST endpoints accept an Idempotency-Key header so you can safely retry on network failure without creating duplicate effects.
Set Idempotency-Key on any state-mutating request to a UUID or other random opaque string (length 1–255):
POST /v1/partners/.../...
Idempotency-Key: 6f8e2c44-a13e-4c8f-9d8b-1c2d3e4f5a6b
Authorization: Bearer tk_live_...
Content-Type: application/json
{ "field": "value" }Behavior
- On first execution, Tendral runs the request and stores (status, headers, body) for 24 hours, keyed on
(api_key_id, idempotency_key). - A retry within the 24-hour window with the same key + same request body returns the original response with header
Idempotent-Replayed: true. - A retry with the same key + different request body returns
409 IDEMPOTENCY_KEY_REUSED(error.type = idempotency_error). The original effect is preserved; the reuse attempt is rejected. - A retry with a fresh key creates a brand-new effect. Always use a fresh key for any new operation.
Recommended pattern
Generate a UUID v4 client-side, retry on network failure or 5xx with exponential backoff, and only mark the operation done after a 2xx (whether or not Idempotent-Replayed is set):
import { randomUUID } from 'node:crypto'
async function callWithRetry(body) {
const key = randomUUID()
for (let attempt = 0; attempt < 5; attempt++) {
const r = await fetch('https://api.tendralhealth.com/v1/partners/.../...', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TENDRAL_API_KEY}`,
'Idempotency-Key': key,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (r.ok) return r.json()
if (r.status >= 400 && r.status < 500) throw new Error(await r.text()) // permanent
await sleep(Math.min(60_000, 1000 * 2 ** attempt) * (0.8 + Math.random() * 0.4))
}
throw new Error('Exceeded retry budget')
}Caveats
- Keys expire after 24 hours. After that, the same key + same body creates a new effect — the dedup record has been swept.
- Keys are scoped per API token. Two different bearer tokens with the same
Idempotency-Keyare independent. - Don't reuse keys across different operations. The body-hash check catches accidental reuse; intentional reuse will fail with
IDEMPOTENCY_KEY_REUSED.
