Skip to content

Quick Start

Basic replay

POST /payments/
Idempotency-Key: key-1
{"amount": 2000}

First request: executes normally, 201 Created, Idempotent-Replayed: false. Any retry with the same key and body: identical 201 Created, Idempotent-Replayed: true, and the view never re-executes.

Key reuse with a different body is rejected

POST /payments/
Idempotency-Key: key-1
{"amount": 5000}

If key-1 was already used for a {"amount": 2000} request, this returns 422 Unprocessable Entity — the key is tied to that specific request, not reusable for a different operation.

Concurrent retries never double-execute

If a client retries while the original request is still processing (a genuine network-level race, not a sequential retry), the second request gets 409 Conflict rather than running the view a second time:

{"detail": "A request with Idempotency-Key 'key-1' is already being processed. Retry after it completes."}

The client should back off briefly and retry again — by then the first request will typically have completed and the retry will get the replayed response instead.

Status tracking

from drf_idempotency import get_idempotency_status

get_idempotency_status("key-1")  # "in_progress", "completed", or None

Useful for a client polling "is my earlier request done yet?" without resending the full request body.

Cleaning up expired records (database backend)

python manage.py cleanup_expired_idempotency_keys

Schedule this via cron or Celery beat — see Deployment. Not needed for RedisBackend, which expires keys natively.

Custom query parameter... there isn't one

Unlike some of this package's siblings, idempotency keys are always read from a request header (per the Stripe convention this package follows), never a query parameter or body field — see FAQ.

Next steps

  • Advanced Usage — writing a custom backend, using Redis, combining with other middleware.
  • Security — what this package does and doesn't protect against.