Guides / Idempotency
Idempotency.
Send an Idempotency-Key header on REST calls that use records or scrape units. A retry with the same key returns the first result and never counts anything twice.
Send a key
Make one key per logical operation, for example a UUID, and reuse it for every retry of that operation. The SDKs already send a key on every POST and reuse it across their own retries; pass your own to make a call safe to repeat across processes.
cURLTypeScriptPython
curl https://api.leadsets.ai/v1/people/enrich \
-H "Authorization: Bearer $LEADSETS_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5d0c9a52-8a3e-4f0b-9b1e-2f6c1d7e8a90" \
-d '{"linkedin_url": "https://www.linkedin.com/in/mayachen"}'import { randomUUID } from "node:crypto";
import { Leadsets } from "@leadsetsai/sdk";
const ls = new Leadsets(process.env.LEADSETS_KEY);
const res = await ls.people.enrich(
{ linkedin_url: "https://www.linkedin.com/in/mayachen" },
{ idempotencyKey: randomUUID() },
);import uuid
import os
from leadsets import Leadsets
ls = Leadsets(os.environ["LEADSETS_KEY"])
res = ls.people.enrich(
linkedin_url="https://www.linkedin.com/in/mayachen",
idempotency_key=str(uuid.uuid4()),
)Rules
- A key is any string up to 255 characters. Longer keys answer 400
invalid_request. - Keys are scoped to your workspace and the endpoint: the same key on two endpoints is two separate keys.
- A successful result is kept for 24 hours. A retry in that time gets the same body back with the header
Idempotent-Replayed: true, and nothing is counted: no records, no request, no scrape units. - Errors are not kept. After a 429, a 5xx or any other error, a retry with the same key runs the call again.
- The same key with a different body answers 409
idempotency_conflict. - While the first call with a key is still running, another call with that key also answers 409
idempotency_conflict ("still running"). The hold lasts up to 120 seconds; wait a moment and retry with the same key. GET /v1/usage ignores the header, since it costs nothing.- MCP tool calls don't take a key. Agents should retry by the error
type, as Errors and retries describes.
A retry loop
Without an SDK, keep the key outside the loop and honour Retry-After:
cURLTypeScriptPython
# curl --retry resends the same headers, so the key stays the same,
# and waits for Retry-After on 429 and 503.
curl https://api.leadsets.ai/v1/people/enrich --retry 3 \
-H "Authorization: Bearer $LEADSETS_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"linkedin_url": "https://www.linkedin.com/in/mayachen"}'import { randomUUID } from "node:crypto";
async function enrich(body: object) {
const key = randomUUID();
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch("https://api.leadsets.ai/v1/people/enrich", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LEADSETS_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(body),
});
if (res.status !== 429 && res.status < 500) return res.json();
const wait = Number(res.headers.get("retry-after") ?? 2 ** attempt);
await new Promise((r) => setTimeout(r, wait * 1000));
}
}import os, time, uuid
import requests
def enrich(body):
key = str(uuid.uuid4())
for attempt in range(5):
res = requests.post(
"https://api.leadsets.ai/v1/people/enrich",
headers={
"Authorization": "Bearer " + os.environ["LEADSETS_KEY"],
"Idempotency-Key": key,
},
json=body,
)
if res.status_code != 429 and res.status_code < 500:
return res.json()
time.sleep(float(res.headers.get("Retry-After", 2 ** attempt)))A full window can mean a long wait. When window_full says the window resets in hours, Retry-After says so too. Decide whether to wait or stop the run; the error body has resets_at.
Was this page useful? Tell us