400 BadRequestError when calling images.generate() with DALL·E-3

Hi everyone,

I’m encountering a blocking issue while trying to generate an image using the OpenAI Python SDK.

Here is the relevant part of my code:

import os

# Initialize client with API key stored in environment variable
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

# Image generation request
generation_response = client.images.generate(
    model="dall-e-3",
    prompt="A cyberpunk monkey dreaming of a beautiful bunch of bananas, digital art",
    n=1,
    size="1024x1024",
    response_format="url",
) 

Each time I run this, I get the following error:

openai.BadRequestError: Error code: 400 - {'error': {'message': None, 'type': 'image_generation_user_error', 'param': None, 'code': None}}

Here’s what I’ve already checked:

  • API key is correctly loaded and I do have all the permissions to access dall-e-3
  • openai Python SDK is up to date (version 1.86.0).
  • Same error occurs with and without explicitly specifying model="dall-e-3", or model=dall-e-2.
  • The payload seems properly formatted (checked via debug logs).
  • No additional parameters are used; this is a minimal example.

Can you see something wrong in my code ? Does the issue come from something else ? Any help will be appreciated.

Thanks.

I do not know if that is the case, but for image/edits endpoint I had the same error for dall-e-2. For gpt-image-1 using SDK from openAI

client.images.(...)

worked fine, unlike dall-e-2. Switching to use requests library in python and use requests.post(…) resolved the problem with dall-e-2. But I used only image.edit

This error message indicates that the extreme, ridiculous (as in subject to my ridicule) moderations blocked the image.

'type': 'image_generation_user_error'

However, I tried a few times, and didn’t get an error. It may have to do with org-specific allowances, or what the prompt rewriter actually changed your prompt into, if it used blocked keywords or similar (you don’t see the rewriting upon failure - an API shortcoming).

(note - you don’t need to include os.environ.get(), as the API library does that for you automatically with the default environment variable)

Have some free monkeys:

Have a procedural incremental script that guards against bad parameters being sent, that was successful for my organization with these options. It will save locally to a subdirectory whether you use the boolean download_by_url to get URL or base64. (also a popup preview of the created image)


'''
DALL-E image generation example for openai>1.8.4, saves requested images as files
'''
## START - global user input data
prompt = (
"""
A cyberpunk monkey dreaming of a beautiful bunch of bananas, digital art
""".strip()
)
model_choice = "dall-e-3"
size_choice = 1             # !! see size table
dalle_3_hd = False          # quality at 2x the price, defaults to "standard"
dalle_3_natural = False     # defaults to "vivid", natural is poor
image1_quality = "medium"   # high, medium, or low
download_by_url = True     # retrieval method; False = get base64 in response
save_directory = "./images"
log_directory = "./logs"    # json of call and response per image
## END - user input data

from io import BytesIO
from datetime import datetime, timezone # for formatting date returned with images
from importlib.metadata import version, PackageNotFoundError  # check w/o import
# other imports lazy-loaded as needed

def is_old_package(version, minimum):
    version_parts = list(map(int, version.split(".")))
    minimum_parts = list(map(int, minimum.split(".")))
    return version_parts < minimum_parts
try:
    openai_version = version("openai")
except PackageNotFoundError:
    raise RuntimeError("The ‘openai’ package is not installed or found. "
                       "You should run 'pip install --upgrade openai")
if is_old_package(openai_version, "1.8.4"):  # the only package needing version check
    raise ValueError(f"Error: OpenAI version {openai.__version__}"
                     " is less than the minimum version 1.8.4\n\n"
                     ">>You should run 'pip install --upgrade openai')")
else:
    from openai import OpenAI, RateLimitError, BadRequestError
    from openai import APIConnectionError, APIStatusError

sizes = {
    "dall-e-2":    {1:"256x256",   2: "512x512",   3: "1024x1024"},
    "dall-e-3":    {1:"1024x1024", 2: "1792x1024", 3: "1024x1792"},
    "gpt-image-1": {1:"1024x1024", 2: "1536x1024", 3: "1024x1536"},
}
api_params = {
 "model": model_choice,     # Defaults to dall-e-2
 "size": sizes[model_choice][size_choice], 
 "prompt": prompt,          # DALL-E 3: max 4000 characters, DALL-E 2: max 1000
 "n": 1,                    # Between 2 and 10 is only for DALL-E 2
 "user": "myname",          # pass a customer ID for safety tracking
}
if model_choice == "gpt-image-1":
    api_params.update({
        "quality": image1_quality,
        "moderation": "low",  # or None = stupidly strict
        "background": "opaque",  # transparent, opaque or auto
        "output_format": "png",  # or jpg, webp
    })     
    if api_params["output_format"] in ["jpg", "webp"]:
        api_params["output_compression"] = "99"  # only jpg, webp
    if download_by_url:
        print("warn: gpt-image-1 can't download via URLs; using base64")
    # gpt-image-1 always returns base64; remove untolerated `response_format` key
    api_params.pop("response_format", None)
else:  # if dall-e
    if download_by_url:
        api_params["response_format"] = "url"
    else:
        api_params["response_format"] = "b64_json"
    if model_choice == "dall-e-3" and dalle_3_hd:
        api_params["quality"] = "hd"
    if model_choice == "dall-e-3" and dalle_3_natural:
        api_params["style"] = "natural"

def prepare_path(path_str):
    """
    Ensure the provided path exists and is writable.
    Creates the directory if it doesn't exist. Tests writability by
    creating and deleting a temporary file. Returns a pathlib.Path.
    """
    from pathlib import Path
    path = Path(path_str)
    # If path is relative, treat as subdirectory of cwd
    if not path.is_absolute():
        path = Path.cwd() / path

    # Create the directory if it doesn't exist
    if not path.exists():
        path.mkdir(parents=True, exist_ok=True)
    elif not path.is_dir():
        raise ValueError(f"'{path}' exists and is not a directory")

    # Test writability by writing and deleting a temp file
    test_file = path / ".write_test"
    try:
        with open(test_file, "w") as f:
            f.write("")  # empty write
        test_file.unlink()
    except Exception as e:
        raise PermissionError(f"Cannot write to directory '{path}': {e}")

    return path

# ensure files can be written before calling
files_path = prepare_path(save_directory)
logs_path = prepare_path(log_directory)

# here's the actual request to API and lots of error-catching
client = OpenAI(timeout=150)  # will use environment variable "OPENAI_API_KEY"
try:
    from time import time
    start = time()
    images_response = client.images.generate(**api_params)
except APIConnectionError as e:
    print("Server connection error: {e.__cause__}")  # passed from httpx
    raise
except RateLimitError as e:
    print(f"OpenAI RATE LIMIT error {e.status_code}: (e.response)")
    raise
except APIStatusError as e:
    print(f"OpenAI STATUS error {e.status_code}: (e.response)")
    raise
except BadRequestError as e:
    print(f"OpenAI BAD REQUEST error {e.status_code}: (e.response)")
    raise
except Exception as e:
    print(f"An unexpected error occurred: {e}")
    raise

# Print the elapsed time of the API call
print(f"Elapsed time: {time()-start:.1f}")

# get the prompt used if rewritten by dall-e-3, null if unchanged by AI
revised_prompt = images_response.data[0].revised_prompt
print(f"Revised Prompt:", revised_prompt)

# get out all the images in API return, whether url or base64
# note images_response being pydantic "model.data" and its model_dump() method
image_url_list = []
image_data_list = []
for image in images_response.data:
    image_url_list.append(image.model_dump()["url"])
    image_data_list.append(image.model_dump()["b64_json"])

# Prepare a list of image file bytes from either URLs or base64 data
image_file_bytes = []

# Download or decode images and append to image_objects (no saving yet)
if image_url_list and all(image_url_list):
    import requests                # for downloading images from URLs
    for url in image_url_list:
        while True:
            try:
                print(f"getting URL: {url}")
                response = requests.get(url)
                response.raise_for_status()  # Raises stored HTTPError, if one occurred.
            except requests.HTTPError as e:
                print(f"Failed to download image from {url}. Error: {e.response.status_code}")
                retry = input("Retry? (y/n): ")  # ask script user if image url is bad
                if retry.lower() in ["n", "no"]:
                    raise
                else:
                    continue
            break
        image_file_bytes.append(response.content)
elif image_data_list and all(image_data_list):  # if there is b64 data
    import base64                  # for decoding images if received in the reply
    for data in image_data_list:
        image_file_bytes.append(base64.b64decode(data))
else:
    raise ValueError("No image data was obtained. Maybe bad code?")

# After obtaining all image objects, produce filenames and save files
def prompt_to_filename(s,max_length=30):
    '''make prompt text safe for use within file names'''
    import unicodedata,re
    t=unicodedata.normalize('NFKD',s).encode('ascii','ignore').decode('ascii')
    t=re.sub(r'[^A-Za-z0-9._\- ]+','_',t)
    t=re.sub(r'_+','_',t).strip(' .')
    if len(t)>max_length: t=t[:max_length].rstrip(' .')
    if not t: return 'prompt'
    r={'CON','PRN','AUX','NUL','COM1','COM2','COM3','COM4','COM5',
       'COM6','COM7','COM8','COM9','LPT1','LPT2','LPT3','LPT4',
       'LPT5','LPT6','LPT7','LPT8','LPT9'}
    if t.upper().split('.')[0] in r: t='_'+t
    return t

# make an auto file name; "created" in response is UNIX epoch time
filename_base = api_params["model"]
epoch_time_int = images_response.created
my_datetime = datetime.fromtimestamp(epoch_time_int, timezone.utc)
if globals().get('i_want_local_time') or True:
    my_datetime = my_datetime.astimezone()
file_datetime = my_datetime.strftime('%Y%m%d-%H%M%S')
short_prompt = prompt_to_filename(prompt)
img_filebase = f"{filename_base}-{file_datetime}-{short_prompt}"
extension = "png"  # or api_params["output_format"] only accepted with gpt-image-1 model

# Initialize an empty list to store the Image objects
image_objects = []
from PIL import Image

for i, img_bytes in enumerate(image_file_bytes):
    img = Image.open(BytesIO(img_bytes))
    image_objects.append({"file":img, "filename": f"{img_filebase}-{i}.{extension}"})

    # build a Path to the output file
    out_file = files_path / f"{img_filebase}-{i}.{extension}"
    img.save(out_file)
    print(f"{out_file} was saved")

# Log the request body parameters and the full response object to a file
log_obj = {"req": api_params, "resp": images_response.model_dump()}
log_filename = logs_path / f"{img_filebase}.json"
# Write the log_obj as JSON to the log file
import json
with open(log_filename, "w", encoding="utf-8") as log_f:
    json.dump(log_obj, log_f, indent=0)


## -- extra fun: pop up some thumbnails in a GUI if you want to see what was saved

from PIL import Image          # pillow, for processing image types
import tkinter as tk           # for GUI thumbnails of what we got
from PIL import ImageTk        # for GUI thumbnails of what we got

def resize_to_max(img, max_w, max_h):
    """
    Return a resized copy of `img` so that neither width nor height
    exceeds (max_w, max_h), preserving aspect ratio.
    """
    w, h = img.size
    scale = min(max_w / w, max_h / h)
    new_w = int(w * scale)
    new_h = int(h * scale)
    return img.resize((new_w, new_h), Image.LANCZOS)

if image_objects:
    for i, img_dict in enumerate(image_objects):
        img = img_dict["file"]
        filename = img_dict["filename"]

        # Resize image for pop-up
        if img.width > 768 or img.height > 768:
            img = resize_to_max(img, 768, 768)

        window = tk.Tk()
        window.title(filename)

        tk_image = ImageTk.PhotoImage(img)
        label = tk.Label(window, image=tk_image)
        label.image = tk_image  # keep a reference so it isn’t garbage-collected
        label.pack()

        window.mainloop()