> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bfl.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# FLUX Deblur

> Sharpen blurry images via API while preserving the original scene, character identity, colors, and composition.

export const ImageComparisonSlider = ({beforeImage, afterImage, beforeLabel = "Before", afterLabel = "After", height = "500px", objectFit = "cover", garmentImage, garmentLabel = "Garment reference"}) => {
  const [position, setPosition] = useState(50);
  const dialogRef = useRef(null);
  const openLightbox = () => {
    if (dialogRef.current && typeof dialogRef.current.showModal === "function") {
      dialogRef.current.showModal();
    }
  };
  const closeLightbox = () => {
    if (dialogRef.current && typeof dialogRef.current.close === "function") {
      dialogRef.current.close();
    }
  };
  const getPosition = (e, container) => {
    const rect = container.getBoundingClientRect();
    const clientX = e.touches ? e.touches[0].clientX : e.clientX;
    const x = clientX - rect.left;
    return Math.max(0, Math.min(100, x / rect.width * 100));
  };
  const onPointerDown = e => {
    e.preventDefault();
    e.stopPropagation();
    const container = e.currentTarget;
    setPosition(getPosition(e, container));
    const onMove = ev => {
      ev.preventDefault();
      setPosition(getPosition(ev, container));
    };
    const onUp = () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("touchmove", onMove);
      window.removeEventListener("touchend", onUp);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    window.addEventListener("touchmove", onMove, {
      passive: false
    });
    window.addEventListener("touchend", onUp);
  };
  return <div className="not-prose" style={{
    borderRadius: "1rem",
    overflow: "hidden",
    height,
    width: "100%"
  }}>
      <div onMouseDown={onPointerDown} onTouchStart={onPointerDown} onClick={e => {
    e.preventDefault();
    e.stopPropagation();
  }} style={{
    position: "relative",
    width: "100%",
    height,
    overflow: "hidden",
    cursor: "ew-resize",
    userSelect: "none",
    WebkitUserSelect: "none"
  }}>
        {}
        <img src={afterImage} alt={afterLabel} draggable={false} style={{
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: "100%",
    objectFit,
    pointerEvents: "none"
  }} />

        {}
        <div style={{
    position: "absolute",
    top: 0,
    left: 0,
    width: "100%",
    height: "100%",
    clipPath: `inset(0 ${100 - position}% 0 0)`
  }}>
          <img src={beforeImage} alt={beforeLabel} draggable={false} style={{
    display: "block",
    width: "100%",
    height: "100%",
    objectFit,
    pointerEvents: "none"
  }} />
        </div>

        {}
        <div style={{
    position: "absolute",
    top: 0,
    left: `${position}%`,
    transform: "translateX(-50%)",
    width: "3px",
    height: "100%",
    background: "rgba(255,255,255,0.85)",
    pointerEvents: "none",
    zIndex: 2
  }} />

        {}
        <div style={{
    position: "absolute",
    top: "50%",
    left: `${position}%`,
    transform: "translate(-50%, -50%)",
    width: "44px",
    height: "44px",
    borderRadius: "50%",
    background: "rgba(255,255,255,0.95)",
    border: "2px solid rgba(0,0,0,0.15)",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "6px",
    zIndex: 3,
    pointerEvents: "none",
    boxShadow: "0 2px 8px rgba(0,0,0,0.3)"
  }}>
          {}
          <svg width="10" height="14" viewBox="0 0 10 14" fill="none" style={{
    marginRight: "-2px"
  }}>
            <path d="M8 1L2 7L8 13" stroke="#333" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          {}
          <svg width="10" height="14" viewBox="0 0 10 14" fill="none" style={{
    marginLeft: "-2px"
  }}>
            <path d="M2 1L8 7L2 13" stroke="#333" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </div>

        {}
        <div style={{
    position: "absolute",
    top: "12px",
    left: "12px",
    padding: "4px 10px",
    borderRadius: "6px",
    background: "rgba(0,0,0,0.55)",
    backdropFilter: "blur(4px)",
    color: "#fff",
    fontSize: "0.75rem",
    fontWeight: 600,
    letterSpacing: "0.02em",
    pointerEvents: "none",
    zIndex: 4
  }}>
          {beforeLabel}
        </div>
        <div style={{
    position: "absolute",
    top: "12px",
    right: "12px",
    padding: "4px 10px",
    borderRadius: "6px",
    background: "rgba(0,0,0,0.55)",
    backdropFilter: "blur(4px)",
    color: "#fff",
    fontSize: "0.75rem",
    fontWeight: 600,
    letterSpacing: "0.02em",
    pointerEvents: "none",
    zIndex: 4
  }}>
          {afterLabel}
        </div>

        {}
        {garmentImage && <div onMouseDown={e => e.stopPropagation()} onTouchStart={e => e.stopPropagation()} onClick={e => {
    e.preventDefault();
    e.stopPropagation();
    openLightbox();
  }} title={`${garmentLabel} — click to enlarge`} style={{
    position: "absolute",
    bottom: "12px",
    right: "12px",
    height: "140px",
    maxWidth: "40%",
    borderRadius: "8px",
    overflow: "hidden",
    cursor: "zoom-in",
    background: "rgba(255,255,255,0.95)",
    border: "2px solid rgba(255,255,255,0.9)",
    boxShadow: "0 2px 12px rgba(0,0,0,0.45)",
    zIndex: 5,
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
            <img src={garmentImage} alt={garmentLabel} draggable={false} style={{
    height: "100%",
    width: "auto",
    objectFit: "contain",
    pointerEvents: "none"
  }} />
          </div>}
      </div>

      {}
      {garmentImage && <dialog ref={dialogRef} onClick={closeLightbox} onClose={closeLightbox} style={{
    padding: 0,
    border: "none",
    background: "transparent",
    maxWidth: "100vw",
    maxHeight: "100vh",
    width: "100vw",
    height: "100vh",
    overflow: "hidden"
  }}>
          <div onClick={closeLightbox} style={{
    width: "100vw",
    height: "100vh",
    background: "rgba(0,0,0,0.88)",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    cursor: "zoom-out",
    padding: "40px",
    boxSizing: "border-box"
  }}>
            <img src={garmentImage} alt={garmentLabel} onClick={e => e.stopPropagation()} style={{
    maxWidth: "90vw",
    maxHeight: "90vh",
    objectFit: "contain",
    borderRadius: "8px",
    boxShadow: "0 8px 32px rgba(0,0,0,0.6)",
    cursor: "default"
  }} />
          </div>
        </dialog>}
    </div>;
};

<Note>
  FLUX Deblur takes a single blurry image and returns a sharper version of the same scene. It is designed to restore detail while staying faithful to the input image, with no prompt and no mask required.
</Note>

## Example output

Drag the slider to compare the blurry input image (left) against the deblurred result (right).

<ImageComparisonSlider beforeImage="https://cdn.sanity.io/images/2gpum2i6/production/64515a784bcb7b8040c82e7e277185bbe73621c8-728x946.png" afterImage="https://cdn.sanity.io/images/2gpum2i6/production/3072f149b0e691d5c6f82f64e2bd7b6341fd1b8c-720x944.png" beforeLabel="Blurry input" afterLabel="Deblurred result" height="560px" objectFit="contain" />

### More examples

<AccordionGroup>
  <Accordion title="Couple selfie">
    <ImageComparisonSlider beforeImage="https://cdn.sanity.io/images/2gpum2i6/production/b7ef49dfda7d4cfd9d5b17a3422ba1908b12a94f-1072x1440.png" afterImage="https://cdn.sanity.io/images/2gpum2i6/production/431ebbea1f36a8b57b464985b9d367a6a1c2f29d-1072x1440.png" beforeLabel="Blurry input" afterLabel="Deblurred result" height="600px" objectFit="contain" />
  </Accordion>

  <Accordion title="UFO scene">
    <ImageComparisonSlider beforeImage="https://cdn.sanity.io/images/2gpum2i6/production/9b1a25f330692df3830e18ff731c535d8ecc81f9-1072x1440.png" afterImage="https://cdn.sanity.io/images/2gpum2i6/production/9139e61b47883cb3bc0a86077befa8448525be04-1072x1440.png" beforeLabel="Blurry input" afterLabel="Deblurred result" height="600px" objectFit="contain" />
  </Accordion>
</AccordionGroup>

## Endpoint

Submit a deblur job:

```http theme={null}
POST https://api.bfl.ai/v1/flux-tools/deblur-v1
x-key: $BFL_API_KEY
```

Poll for the result:

```http theme={null}
GET https://api.bfl.ai/v1/get_result?id=<TASK_ID>
x-key: $BFL_API_KEY
```

## Quick start

The API uses an asynchronous workflow:

<Steps>
  <Step title="Submit a deblur request">
    POST your blurry input image to the endpoint as base64 or an HTTP(S) URL. The model sharpens the whole image automatically - no prompt or mask is accepted.
  </Step>

  <Step title="Poll for the result">
    Use the returned `polling_url` to check status until the deblurred image is ready.
  </Step>
</Steps>

<CodeGroup>
  ```python Python theme={null}
  #!/usr/bin/env python3
  import base64
  import os
  import time
  import requests

  API_KEY = os.environ["BFL_API_KEY"]
  BASE = "https://api.bfl.ai"
  HEADERS = {"accept": "application/json", "x-key": API_KEY, "Content-Type": "application/json"}

  IMAGE_PATH = "/path/to/blurry-input.png"

  with open(IMAGE_PATH, "rb") as f:
      image_b64 = base64.b64encode(f.read()).decode()

  payload = {
      "image": image_b64,
      "output_format": "png",
  }

  submit = requests.post(f"{BASE}/v1/flux-tools/deblur-v1", headers=HEADERS, json=payload)
  submit.raise_for_status()
  meta = submit.json()

  task_id = meta["id"]
  poll_url = meta.get("polling_url", f"{BASE}/v1/get_result?id={task_id}")

  while True:
      r = requests.get(poll_url, headers={"accept": "application/json", "x-key": API_KEY})
      r.raise_for_status()
      result = r.json()

      status = result.get("status")
      if status == "Ready":
          print("Result URL:", result["result"]["sample"])
          break
      if status in {"Error", "Request Moderated", "Content Moderated", "Task not found"}:
          raise RuntimeError(f"Deblur failed with status: {status} | payload: {result}")

      time.sleep(1)
  ```

  ```bash cURL theme={null}
  IMAGE_B64="$(base64 < /path/to/blurry-input.png | tr -d '\n')"

  jq -n \
    --arg img "$IMAGE_B64" \
    '{
      image: $img,
      output_format: "png"
    }' > /tmp/deblur_request.json

  curl -sS -X POST "https://api.bfl.ai/v1/flux-tools/deblur-v1" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-key: $BFL_API_KEY" \
    --data-binary @/tmp/deblur_request.json

  # Poll for the result
  curl -sS "https://api.bfl.ai/v1/get_result?id=YOUR_TASK_ID" \
    -H "accept: application/json" \
    -H "x-key: $BFL_API_KEY"
  ```
</CodeGroup>

## Request parameters

Use `image` as the minimum payload.

| Parameter          | Type    | Required | Description                                                                      |
| ------------------ | ------- | -------- | -------------------------------------------------------------------------------- |
| `image`            | string  | Yes      | Blurry input image as base64 or an HTTP(S) image URL. Maximum image area is 4 MP |
| `seed`             | integer | No       | Optional seed for reproducibility                                                |
| `safety_tolerance` | integer | No       | `0-5`, defaults to `2`. Moderation strictness for input and output               |
| `output_format`    | string  | No       | `png` (default), `jpeg`, or `webp`                                               |
| `webhook_url`      | URL     | No       | Async callback                                                                   |
| `webhook_secret`   | string  | No       | Signature secret                                                                 |

No `prompt` or `mask` parameter is sent by the caller. The server applies a fixed deblurring instruction to the whole image.

## Response format

### Initial response

```json theme={null}
{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "polling_url": "https://api.bfl.ai/v1/get_result?id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```

Always poll the URL returned in the response.

### Polling response (success)

```json theme={null}
{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "Ready",
  "result": {
    "sample": "https://delivery.bfl.ai/..."
  }
}
```

When `status` is `"Ready"`, use `result.sample`.

<Warning>
  Signed delivery URLs are only valid for **10 minutes**. Retrieve your result within this timeframe.
</Warning>

## How it works

FLUX Deblur treats blur removal as a guided image edit. It reconstructs fine detail while staying anchored to the input, so character identity, colors, objects, lighting, and composition remain faithful to the original scene instead of being re-imagined.

The endpoint is powered by a FLUX.2 Klein 9B image-to-image finetune trained on blurry-to-sharp image pairs. Because it uses the Klein backbone, it is suitable for low-latency, lower-cost, high-volume, or interactive workflows.

## Tips for best results

* Use images where the underlying scene is still recognizable. Heavy blur can be reduced substantially, but the endpoint is not a perfect restorer for severely degraded inputs.
* Run deblur before downstream cleanup steps such as upscaling, outpainting, or publication export.

## Limitations

* Heavily degraded inputs may contain residual blur or reconstructed details that are not exact.
* Since the endpoint is based on Klein, typography and human anatomy can be faulty, especially for heavily blurred images.
* Deblur is applied to the full image. It does not support region-specific masks.

## Troubleshooting

* **`403 Forbidden`** - your API key is missing or your project does not have access to this endpoint.
* **`422` / validation errors** - check base64 encoding, image URL accessibility, and the 4 MP maximum image area.
* **Result still looks blurry** - the input may be too degraded; try using the least compressed source available.
* **Unexpected artifacts** - try a cleaner source image or run deblur before other enhancement steps.

For the full list of HTTP status codes and polling response types returned by the API, see the [Errors reference](/api_integration/errors).
