odysseus-dev/odysseus · error · HTTPException

OpenAI edit failed

Error message

OpenAI edit failed

What it means

Raised inside a FastAPI proxy route when the upstream OpenAI-compatible /v1/images/edits endpoint answers with a non-200 status. The route forwards the upstream status code verbatim as the HTTPException status, with the generic detail 'OpenAI edit failed'. It means the multipart edit request reached the server but was rejected or failed server-side.

Source

Thrown at routes/gallery/gallery_routes.py:1380

            }
            # Honor explicit model selection from the editor; fall back to gpt-image-1.
            # dall-e-3 has no edit endpoint — refuse it loudly so the user picks again.
            oa_model = chosen_model or "gpt-image-1"
            if "dall-e-3" in oa_model:
                raise HTTPException(400, "dall-e-3 doesn't support image edits — pick gpt-image-1 or dall-e-2")
            data = {
                "model": oa_model,
                "prompt": body.get("prompt", ""),
                "size": size,
                "n": "1",
            }
            headers = {"Authorization": f"Bearer {api_key}"}
            try:
                async with httpx.AsyncClient(timeout=120) as client:
                    r = await client.post(_join_checked_gallery_endpoint(base, "/images/edits"), headers=headers, data=data, files=files)
                    if r.status_code != 200:
                        logger.error("inpaint_proxy OpenAI edit: status %s", r.status_code)
                        raise HTTPException(r.status_code, "OpenAI edit failed")
                    result = r.json()
                    raw_b64 = None
                    if result.get("data"):
                        item = result["data"][0]
                        # gpt-image-1 returns b64_json by default; dall-e-2 may return url
                        if item.get("b64_json"):
                            raw_b64 = item["b64_json"]
                        elif item.get("url"):
                            raw_b64 = await _fetch_result_image_b64(item["url"])
                    if not raw_b64:
                        raise HTTPException(502, "OpenAI returned no image")

                    # OpenAI's edits API doesn't truly preserve unmasked
                    # pixels — gpt-image-1 regenerates the whole image,
                    # so even areas the user didn't mask come back
                    # slightly different. Composite the model output onto
                    # the ORIGINAL source using the user's mask, so only
                    # the masked region actually changes.

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the server log line 'inpaint_proxy OpenAI edit: status %s' to get the exact upstream code, then fix accordingly (401 → rotate API key, 429 → quota, 400 → payload/model).
  2. Re-verify the configured image endpoint's base_url and api_key in the gallery endpoint settings (must be a valid OpenAI-compatible base ending before /v1).
  3. Confirm the model chosen (oa_model) is one the upstream account can use for image edits.
  4. If upstream is self-hosted (not OpenAI), ensure it actually implements /v1/images/edits or let the route take the diffusion-server path instead.

Example fix

// before
if r.status_code != 200:
    logger.error("inpaint_proxy OpenAI edit: status %s", r.status_code)
    raise HTTPException(r.status_code, "OpenAI edit failed")

// after — surface the upstream detail so the client sees the real cause
if r.status_code != 200:
    detail = "OpenAI edit failed"
    try:
        detail = r.json().get("error", {}).get("message", detail)
    except Exception:
        pass
    logger.error("inpaint_proxy OpenAI edit: status %s %s", r.status_code, detail)
    raise HTTPException(r.status_code, detail)
Defensive patterns

Strategy: try-catch

Validate before calling

import httpx
# Smoke-test credentials/model before launching the proxy call
async def endpoint_ready(base: str, api_key: str, model: str) -> bool:
    try:
        async with httpx.AsyncClient(timeout=15) as c:
            r = await c.get(f"{base}/models", headers={"Authorization": f"Bearer {api_key}"})
            if r.status_code != 200:
                return False
            return any(m.get("id") == model for m in r.json().get("data", []))
    except httpx.HTTPError:
        return False

Try / catch

try:
    resp = await client.post("/api/image/inpaint", json=payload)
    resp.raise_for_status()
except httpx.HTTPStatusError as e:
    detail = e.response.json().get("detail", "")
    if "OpenAI edit failed" in str(detail):
        # inspect server log for the upstream status; 401/429 need user action
        show_error(f"Upstream image edit rejected ({e.response.status_code}): {detail}")
    else:
        raise

Prevention

When it happens

Trigger: POST to the gallery inpaint proxy (inpaint_proxy) that takes the OpenAI path: a valid api_key endpoint base ending in /v1, image+mask files assembled, and the POST to {base}/images/edits returning e.g. 401 (bad key), 400 (malformed image/mask or bad model name), 429 (rate limit), or 5xx.

Common situations: Expired or revoked OpenAI API key stored in the endpoint DB row; model name not available to the account (e.g. gpt-image-1 not entitled); mask PNG not the same dimensions as the image; quota exhaustion returning 429; pointing the endpoint at a non-OpenAI server that returns errors on /images/edits.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/54e472b21773e76a. Report an issue: GitHub.