Async jobs and idempotency
Idempotency-Key
Send an Idempotency-Key header on any /decide call. The first call stores a hash of the request with the key; a retry with the same key and body returns the first answer, with header Idempotent-Replayed: true, without deciding or billing again.
| Situation | Answer |
|---|---|
| Same key, same body | The stored answer |
| Same key, different body | 409 idempotency_key_reused |
| Same key while the first call is still running | 409 in_progress (sync) or the job (async) |
Keys are per account. Use your own operation id (auth_88231, case_771_stmt_3). The SDKs send one automatically on retries.
Async decisions
"async": true answers 202 at once with a job:
{"id": "job_01JC...", "object": "job", "status": "queued", "decision_type": "statement",
"created_at": "2026-11-02T17:40:00+00:00", "url": "/api/v1/jobs/job_01JC..."}
Poll GET /api/v1/jobs/{id} (queued → running → succeeded with the full decision under decision, or failed with error), or register the webhooks decision.completed and decision.failed (signed HMAC-SHA256; they carry the job id, decision id, decision and type, never the input).
job = client.decide("statement", big_input, run_async=True)
d = client.jobs.wait(job.id, timeout=300)
const job = await client.decide("statement", bigInput, { async: true });
const d = await client.jobs.wait(job.id, { timeout: 300_000 });
Billing, refunds on failure and the decision record are the same as a synchronous call. A job interrupted by a restart is reported failed: interrupted after 15 minutes; retry with the same key. Answers are kept 24 hours.