Safe Retries with Idempotency Keys
Send write requests safely. An Idempotency-Key header makes POST and PATCH retryable without duplicates.
Network calls fail. The original request might have succeeded but the response was lost on the wire, leaving the client unsure whether to retry. Naive retries create duplicates, two agents instead of one, two refunds, two emails.
The fix is the Idempotency-Key request header. Pass any unique string with your POST or PATCH request, and Macha will replay the cached response on retry within 24 hours instead of running the operation twice.
How it works
- You send a
POST/PATCHwithIdempotency-Key: <your-unique-string>. - Macha computes a fingerprint from the request method, path, and body. It checks if it has seen this key before.
- First request: no cached record. Macha runs the operation, stores
(key, request_hash, status, response_body), and returns the response. - Retry with the same key and same body: Macha finds the cached record, verifies the hash matches, and returns the stored response. The operation does NOT run again.
- Retry with the same key but a different body: hash mismatch. Macha returns
409 idempotency_key_reused, you've used the same key for a different operation, which is almost certainly a bug.
Cached responses live for 24 hours, then drop out via a TTL index. Reusing the same key after 24h is fine, it'll execute fresh.
Where it applies
| Method | Honors Idempotency-Key? |
|---|---|
| GET | No, reads are naturally idempotent. |
| POST | Yes. |
| PATCH | Yes. |
| DELETE | No, delete-the-thing operations are naturally idempotent (second call returns 404). |
Picking a key
Macha doesn't care what the string looks like, as long as it's:
- Unique per logical operation
- 1–128 characters of ASCII
- Generated and stored by you (so retries can reuse it)
The standard choice is a UUID v4:
Idempotency-Key: 7f6c8a3e-7c5b-4d2c-aa3e-1a9b3d2e7f1a
Other reasonable choices:
- Your own business event ID (e.g.
order-12345-create-agent) - A hash of (user ID + timestamp + action)
- A monotonic ID from your queue system
Anti-patterns
A single Idempotency-Key represents one operation. Sending the same key with a different body returns 409 idempotency_key_reused. Generate fresh keys for fresh operations.
- Bad: reusing the same key across users (
create-agent). Two different users hit the same cached response. - Bad: reusing the same key across attempts at slightly different operations (e.g. retried POST with the user's typo-corrected name). Macha returns 409 because the body hash differs.
- Bad: generating a fresh key on every retry. Defeats the point, Macha can't recognize the retry, and you may create duplicates.
Examples
Curl
curl -X POST https://dashboard.getmacha.com/api/v1/agents \
-H "Authorization: Bearer $MACHA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"handle": "wismo",
"name": "WISMO",
"instructions": "Answer Where Is My Order questions."
}'
Node.js with retry
import { randomUUID } from 'crypto';
async function createAgentWithRetry(body) {
// Generate the key ONCE, outside the retry loop.
const idempotencyKey = randomUUID();
for (let attempt = 0; attempt < 5; attempt++) {
try {
const res = await fetch('https://dashboard.getmacha.com/api/v1/agents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MACHA_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey, // same on every retry
},
body: JSON.stringify(body),
});
if (res.ok) return await res.json();
if (res.status < 500 && res.status !== 429) {
// Non-retryable error
throw new Error(`Macha: ${res.status} ${await res.text()}`);
}
} catch (err) {
if (attempt === 4) throw err;
}
// Exponential backoff with jitter
await new Promise(r => setTimeout(r, 1000 * 2 ** attempt + Math.random() * 250));
}
}
Python with retry
import os, uuid, time, requests
def create_agent_with_retry(body):
idempotency_key = str(uuid.uuid4()) # outside the loop
headers = {
'Authorization': f'Bearer {os.environ["MACHA_API_KEY"]}',
'Content-Type': 'application/json',
'Idempotency-Key': idempotency_key,
}
for attempt in range(5):
r = requests.post(
'https://dashboard.getmacha.com/api/v1/agents',
json=body, headers=headers,
)
if r.ok: return r.json()
if r.status_code < 500 and r.status_code != 429:
r.raise_for_status()
time.sleep(min(2 ** attempt, 30))
raise RuntimeError('Retry budget exhausted')
What gets compared
Macha hashes the SHA-256 of method + path + JSON-serialized body. Two requests are "the same" if all three match exactly. Two requests differ if any of:
- HTTP method is different (e.g. POST vs PATCH)
- URL path is different (e.g.
/agentsvs/agents/abc) - Request body bytes differ, including whitespace, key order, etc.
Query strings and headers are NOT part of the hash. If you pass ?dry_run=true vs not, that's the same operation as far as idempotency is concerned. Be deliberate about which params live in the URL vs the body.
Race handling
Two concurrent requests with the same key both miss the cache, both run, and both try to insert the cache record. Macha uses a unique index on (org_id, key) so exactly one insert wins; the loser catches the duplicate-key error and quietly drops its insert.
Both requests still get their full response (Macha doesn't try to serialize concurrent execution). If your operation has side effects you need to be careful about, charging a payment, sending an email, ensure the operation itself is conditionally idempotent at the application level, not just at the API caching layer.
Limits and rules
- TTL: 24 hours from the first request.
- Key length: 1–128 characters.
- Per-org scope: Keys are namespaced per organization. The same string in two different orgs is two different keys.
- Per-method scope: Sending the same key with
POSTand thenPATCHcounts as different operations and returns409 idempotency_key_reusedon the second call.
When NOT to use idempotency keys
Reads. You'd be polluting the cache for no benefit, GET is already safe to retry.
You also don't strictly need them for first-attempt write requests if you're comfortable handling duplicates yourself. But adding the header costs nothing, so the boring answer is "always set Idempotency-Key on writes." Defense in depth.
© 2026 AGZ Technologies Private Limited