Idempotency
The SDK auto-generates an Idempotency-Key header (UUID v4) on every
POST, PUT, PATCH, and DELETE request. If a network blip
causes you to retry, the second request lands on the same key as the
first — the server returns the original response instead of double-creating.
You only need to think about idempotency keys when you want end-to-end idempotency across your own retries, not just the SDK's internal ones.
Auto-generated keys (the default)
from thrustlab import Client
client = Client()
sim = client.simulations.create(project_id="proj_xxx", ...)
Behind the scenes:
- The SDK generates a UUID v4 (
Idempotency-Key: 7b9...). - If the request fails on a retryable error (429/5xx/network), the same key is reused on every retry.
- The server stores
(key, request_body_hash, response)for 24 hours. Replays of an exact match return the cached response.
This makes the SDK safe under any transient failure without any work from you.
Explicit keys (end-to-end idempotency)
When your own orchestrator might re-run a job after a crash or a queue re-delivery, pass an explicit key tied to your job's identity. That way, re-running the job lands on the same server-side record, even from a fresh process with a fresh SDK client.
sim = client.simulations.create(
project_id="proj_xxx",
battery_component_id="comp_batt_xxx",
airspeed_m_s=0.0, density_kg_m3=1.225, battery_charge_pct=100,
rotor_groups=[{
"label": "main", "count": 4,
"motor_component_id": "comp_motor_xxx",
"propeller_component_id": "comp_prop_xxx",
"throttle_pct": 70,
}],
idempotency_key="job-2026-04-28-sim-001", # tied to your job id
)
Every mutation method on every resource accepts idempotency_key=:
project = client.projects.create(name="Q3 study", idempotency_key="job-2026-04-28-proj")
client.projects.update("proj_xxx", name="Q3 study v2", idempotency_key="job-2026-04-28-rename")
endpoint = client.webhook_endpoints.create(
url="https://...", events=["simulation.completed"],
idempotency_key="setup-2026-04-28",
)
Key requirements
- Unique per logical operation. Reusing the same key for a different
intent (a different request body) fails with a
409 idempotency_error, codeidempotency_key_reused_with_different_body. - Up to 255 characters. UUIDs, KSUIDs, and "job-id-step-name" patterns all work. Random strings are fine.
- Valid for 24 hours. After 24 hours the server forgets the key; reusing it then is treated as a fresh request.
What "same response" means
The server replays the response byte-for-byte within the 24-hour
window — including HTTP status, headers, and body. So if the original
returned 201 Created with a freshly-minted resource, every replay returns
that same resource id and the same body. This is the contract you're
relying on when retrying after a network blip.
Recipe: idempotent fan-out from a queue worker
from thrustlab import Client
client = Client()
def handle_job(job):
"""Submit a sim from a queue job. Re-deliveries are safe."""
sim = client.simulations.create(
project_id=job["project_id"],
idempotency_key=f"sim-from-job-{job['id']}",
**job["params"],
)
return sim["id"]
If the queue redelivers job (because the worker crashed before ack), the
second call returns the same sim resource — no double-charge.
See also
- Idempotency guide — full HTTP-level contract
- Retries & timeouts — what the SDK does on network blips
- Errors — the
idempotency_key_reused_with_different_bodycode