> ## 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 Erase

> Remove objects from a photo via API — fast, mask-driven cleanup powered by FLUX.2 Klein 9B.

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 Erase removes the masked object and reconstructs the scene behind it with contextually coherent content in a single call. Useful for product photography clean-up, removing unwanted elements, scene simplification, or privacy-aware image editing.
</Note>

## Example output

Drag the slider to compare the input image with the masked region outlined in green (left) against the cleaned result (right).

<ImageComparisonSlider beforeImage="https://cdn.sanity.io/images/2gpum2i6/production/36f38bc8b5385068efd67890cb2e9a9d24043b9f-1200x1600.jpg?w=1206&h=1600&fit=crop" afterImage="https://cdn.sanity.io/images/2gpum2i6/production/c2a733e9fec50ea519f9aef25b75fd4482c2d0ef-1206x1600.jpg" beforeLabel="Input with mask outline" afterLabel="Cleaned result" height="600px" objectFit="contain" />

### More examples

<AccordionGroup>
  <Accordion title="Bar table cleanup">
    <ImageComparisonSlider beforeImage="https://cdn.sanity.io/images/2gpum2i6/production/546357c07e51bd06a64be966f9439d7979767f0f-1199x1600.jpg?w=1211&h=1600&fit=crop" afterImage="https://cdn.sanity.io/images/2gpum2i6/production/1928b6d30db6d75e7d6c06a0b614cfd31e6d101c-1211x1600.jpg" beforeLabel="Input with mask outline" afterLabel="Cleaned result" height="600px" objectFit="contain" />
  </Accordion>

  <Accordion title="Beach group, fourth person removed">
    <ImageComparisonSlider beforeImage="https://cdn.sanity.io/images/2gpum2i6/production/902463514f0c6608cf513a6133433ab6b26ca25b-1600x1397.jpg?w=1600&h=1389&fit=crop" afterImage="https://cdn.sanity.io/images/2gpum2i6/production/b740148749bc12a16231addb35547f5867a6328a-1600x1389.jpg" beforeLabel="Input with mask outline" afterLabel="Cleaned result" height="600px" objectFit="contain" />
  </Accordion>
</AccordionGroup>

## Endpoint

Submit an erase job:

```http theme={null}
POST https://api.bfl.ai/v1/flux-tools/erase-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="Prepare a mask">
    Create a black/white PNG at the same resolution as your input image. **White (255)** marks the pixels to remove, **black (0)** marks pixels to keep.
  </Step>

  <Step title="Submit an erase request">
    POST the input image and mask (both base64-encoded) to the endpoint. No prompt is needed — the model uses a built-in erase instruction.
  </Step>

  <Step title="Poll for the result">
    Use the returned `polling_url` to check status until the 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/input.png"
  MASK_PATH = "/path/to/mask.png"  # White (255) = remove, Black (0) = keep
  DILATE_PIXELS = 10               # Expand mask edges for cleaner removal

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

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

  payload = {
      "image": image_b64,
      "mask": mask_b64,
      "dilate_pixels": DILATE_PIXELS,
      "output_format": "png",
  }

  submit = requests.post(f"{BASE}/v1/flux-tools/erase-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"Erase failed with status: {status} | payload: {result}")

      time.sleep(1)
  ```

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

  jq -n \
    --arg img "$IMAGE_B64" \
    --arg mask "$MASK_B64" \
    '{
      image: $img,
      mask: $mask,
      dilate_pixels: 10,
      output_format: "png"
    }' > /tmp/erase_request.json

  curl -sS -X POST "https://api.bfl.ai/v1/flux-tools/erase-v1" \
    -H "accept: application/json" \
    -H "Content-Type: application/json" \
    -H "x-key: $BFL_API_KEY" \
    --data-binary @/tmp/erase_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

| Parameter       | Type          | Required | Description                                                                                                    |
| --------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `image`         | base64 string | Yes      | Input image                                                                                                    |
| `mask`          | base64 string | Yes      | Black/white mask. White (255) = remove, black (0) = keep. Must match the input image dimensions                |
| `dilate_pixels` | integer       | No       | Pixels to dilate the mask before removal. Range `0–25`, default `10`. Helps the model fully cover object edges |
| `output_format` | string        | No       | `png` (default) or `jpeg`                                                                                      |

No prompt is sent by the caller — the server applies a fixed erase instruction internally.

## 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"
}
```

### 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>

## Mask guidelines

* **Format**: black-and-white PNG at the same resolution as the input image.
* **White (255)** = pixels to remove. **Black (0)** = pixels to keep.
* The server converts the binary mask to a green fill internally — callers only send the binary mask.
* **Dilation** (`dilate_pixels`): expanding the mask by a few pixels typically improves removal quality by ensuring the model fully covers object edges. For typical SAM (Segment Anything) masks, start with `10`.

## Tips for best results

* Use a mask that fully covers the object you want to remove. If the mask is leaving an edge, increase `dilate_pixels`.
* For objects with **soft edges** (hair, fur, smoke) that masks rarely capture cleanly, use a higher `dilate_pixels` value (15–20).
* The model was trained on images at **\~1 megapixel** across 9 aspect ratios from 1:2 to 2:1. Inputs close to these resolutions produce the best results — significant deviations may reduce quality.

## Troubleshooting

* **`403 Forbidden`** — your API key is missing or your project doesn't have access to this endpoint.
* **`422` / validation errors** — check base64 encoding and that the mask matches the input image dimensions exactly.
* **Visible halo around removed object** — increase `dilate_pixels`.
* **Reconstruction looks off** — verify the mask covers the entire object including shadows or reflections you want gone.

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