Idempotency
A network request can fail after it was processed: your client then cannot tell whether the address, rule or channel was created. The Idempotency-Key header lets you retry the same request without risking a duplicate.
Usage#
Add a unique value, generated by your client (a UUID is recommended), to every POST, and reuse it for every new attempt of the same operation:
curl -X POST https://aicscan.io/v1/addresses \
-H "Authorization: Bearer $AICSCAN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2b8e-3d4a-4f0e-9b1a-2c3d4e5f6a7b" \
-d '{"chain_id":56,"address":"0x8894E0a0c962CB723c1976a4421c95949bE2D4E3","label":"Treasury"}'- Value: 1 to 255 visible ASCII characters. Otherwise:
400 invalid_idempotency_key. - Scope: idempotency keys are specific to your workspace; two workspaces can use the same value without interfering.
- Lifetime: the response is stored for 24 hours.
- Methods:
POSTonly.PATCHandDELETEare naturally idempotent; the header is ignored there.
Behaviour#
| Situation | Response |
|---|---|
| First request | Processed normally; the response (status, body, Location header) is stored. |
| Same value, same request | The original response is returned as is, with Idempotent-Replayed: true. Nothing is created again. |
| Same value, different request | 422 idempotency_key_reused. |
| Original request still running | 409 idempotency_in_progress with Retry-After: 1: retry. |
| Server error (5xx) | Nothing is stored: retry with the same value. |
"Same request" means same method, same path with its query string, and same body byte for byte. A 4xx error (for example 422 validation_failed) is stored like a success: fix the request and use a new value.
The body of a request sent with an Idempotency-Key is limited to 1 MiB (413 payload_too_large beyond).
Secrets shown only once#
A webhook's signing secret (webhook_secret) is never stored. Replaying a webhook creation returns the same channel with "webhook_secret": null: if you lost the first response, delete that channel and create another one.
Retry example#
import { randomUUID } from 'node:crypto';
async function createRule(rule) {
const key = randomUUID(); // the same for every attempt
for (let attempt = 0; ; attempt++) {
try {
const res = await fetch('https://aicscan.io/v1/rules', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.AICSCAN_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify(rule),
});
if (res.status >= 500 || res.status === 409 || res.status === 429) throw new Error(String(res.status));
return await res.json();
} catch (e) {
if (attempt >= 4) throw e;
await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
}
}
}import os, time, uuid
import requests
def create_rule(rule):
key = str(uuid.uuid4()) # the same for every attempt
headers = {"Authorization": f"Bearer {os.environ['AICSCAN_API_KEY']}", "Idempotency-Key": key}
for attempt in range(5):
try:
resp = requests.post("https://aicscan.io/v1/rules", json=rule, headers=headers, timeout=10)
if resp.status_code < 500 and resp.status_code not in (409, 429):
return resp.json()
except requests.RequestException:
pass
time.sleep(2 ** attempt * 0.5)
raise RuntimeError("Aicscan API unavailable")