Introducing Highlights: the context that matters

HTTP 409 Conflict: What It Means and How to Fix It

HTTP 409 Conflict is a status code meaning the request could not be completed because it conflicts with the current state of the resource, for example two clients editing the same record or a version mismatch on a PUT. The response body should explain the conflict so the client can resolve it and send the request again.

Code
409
Name
Conflict
Class
4xx client error
Retry?
No, fix the cause first

What causes a 409 error?

  • →Two clients updating the same record, where the second write is based on stale data.
  • →Creating a resource that already exists, such as a duplicate username or ID.
  • →A version or ETag mismatch in an optimistic-locking API.
  • →An operation that is not valid in the resource’s current state, such as cancelling a finished job.

How do you fix a 409 error when web scraping?

  • →Scraping is mostly read-only, so a 409 usually comes from a write step in your pipeline, such as an upsert into your own API or database.
  • →Fetch the current state, merge your change, and resend with the new version or ETag.
  • →Make writes idempotent with stable IDs so a retried job does not create duplicates.

How do you fix a 409 error on your own server?

  • →Explain the conflict in the body and include the current version so clients can reconcile.
  • →Use If-Match with ETags for optimistic locking, and 412 Precondition Failed when the precondition does not hold.

How do you handle a 409 error in a retry loop?

409 is not in RETRYABLE, so raise_for_status() raises on the first response instead of spending retries on a request that will fail the same way. Fix the cause, then send the request again.

import random
import time

import requests

RETRYABLE = {408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524}


def fetch(url: str, max_attempts: int = 5) -> requests.Response:
    for attempt in range(max_attempts):
        try:
            response = requests.get(url, timeout=(10, 60))
        except requests.Timeout:
            time.sleep(2**attempt + random.uniform(0, 1))
            continue
        if response.status_code not in RETRYABLE:
            response.raise_for_status()
            return response
        retry_after = response.headers.get("Retry-After", "")
        backoff = 2**attempt + random.uniform(0, 1)
        time.sleep(min(int(retry_after) if retry_after.isdigit() else backoff, 60))
    raise RuntimeError(f"Gave up on {url} after {max_attempts} attempts")

How does Context.dev handle a 409 error?

Context.dev’s scraping endpoints read pages and do not modify them, so 409 conflicts from target sites are rare. The Context.dev SDKs retry twice with exponential backoff after connection errors and 408, 409, 429, and 5xx responses from the API itself.

See what the web scraping API does on every request, or read how to fix HTTP errors in web scraping for a longer walkthrough.

Frequently asked questions about a 409 error

What does status code 409 mean?

The server refused the request because it conflicts with the resource’s current state, most often a concurrent edit or a duplicate create.

Should I retry a 409?

Not blindly. Reload the current state, resolve the conflict, then resend. Some APIs use 409 for short lock timeouts, and those can be retried after a delay.

What is the difference between 409 and 422?

A 409 is about state: the request is valid but clashes with what is there now. A 422 is about content: the request itself fails validation.

Which status codes are related to 409?

Sources

Last reviewed

Ship an agent that actually knows things.

Free tier, 10-minute integration, and the same API powering agents at Mintlify, daily.dev, and Propane. No credit card to start.