Image Gen: OpenAI Backend (Real Key, Not Codex OAuth)
The working image-gen backend for ABS: the openai Python package driven by OPENAI_API_KEY, the model gpt-image-2-medium, ~$0.04 per 1024x1024 image, and why openai-codex OAuth is broken.

Two image-gen backends ship with ABS, and only one of them answers today. The openai plugin — the plain Python openai package driven by a real OPENAI_API_KEY — generates images on demand. The openai-codex plugin — the ChatGPT-subscription OAuth path that lets you bill against your Plus/Pro seat — fails at the handshake every time. If you are shipping ABS featured illustrations, Open Graph cards, or social banners this week, this is the guide that actually puts an image on disk. The companion failover guide explains where this backend sits in the larger chain.
Why this is the working backend
ABS ships two image-gen plugin paths in /opt/hermes/plugins/image_gen/:
openai/— Pythonopenaipackage, realOPENAI_API_KEY, working today.openai-codex/— ChatGPT subscription OAuth path, broken as of 2026-07-14 (handshake fails).
For any image-gen work — batch featured illustrations, Open Graph images, social cards — the openai plugin is the only path. This guide documents what that path does and how to use it.
The reason both paths exist in the same tree is historical. ABS’s first image-gen integration followed the OAuth route because it avoided per-image billing — a ChatGPT Plus subscription comes with image generation included, and reusing that seat for ABS content was attractive. The path never fully stabilized: the OAuth flow that ChatGPT exposes for first-party apps is the same one third-party clients depend on, and changes to it have broken ABS’s handshake three times in the past year. The current break (content-length mismatch or 502 on the callback) is the third. Re-forking the path is a poor use of operator time when the simpler Python-package path sits one directory over and answers every time.
The openai plugin does what most operators expect anyway: pay-as-you-go at OpenAI’s published API rates, billed to the key’s account, no subscription gymnastics. For a shop that publishes a handful of guides per day, the cost is rounding error. For a shop publishing at higher volume, the API path makes the cost predictable — which matters when you are running monthly budget reviews against the AI Model Matrix.
The 3-line setup
# 1. API key
echo 'OPENAI_API_KEY=sk-...' > /opt/data/.env
chmod 600 /opt/data/.env
# 2. Verify Python package
/opt/hermes/.venv/bin/python -c "import openai; print(openai.__version__)"
# 3. Test the plugin
/opt/hermes/.venv/bin/python -c "
from plugins.image_gen.openai import OpenAIImageGenProvider
r = OpenAIImageGenProvider().generate('a red square', 'square')
print(r['image'])
"
If step 3 returns a path to a generated image, the backend is live.
What model the backend uses
The plugin is pinned to gpt-image-2-medium — a 1024×1024 base, mid-quality model. Two params set per call:
prompt— the full text-image prompt (the LOCKED editorial style block + the per-batch subject).aspect_ratio—"landscape"(16:9, the default for ABS),"portrait", or"square".
The pricing is roughly $0.04 per 1024×1024 landscape. ABS’s batch scripts typically run 3 images per batch (PNG + WebP for one guide’s cover), so a batch of 3 guides costs ~$0.36.
A few things worth knowing about the model choice. gpt-image-2-medium sits in the middle of OpenAI’s image lineup: cheaper than the high-quality variant, more reliable than the base. ABS’s editorial style block — heavy ink outlines, halftone texture, restricted palette — works at this quality tier because the style itself does the heavy lifting; a higher-resolution model would not produce visibly better output for the same prompt, and a lower one would lose the ink outline confidence that defines the look. If a future guide needs photographic realism rather than editorial illustration, the medium tier is the wrong pick and the script should pin a different model — but for ABS’s current cover-art direction, medium is the right default.
The aspect-ratio parameter is not a hint — it is a constraint. ABS’s lockup assumes 16:9 landscape, and deviating from that produces covers that no longer fit the site’s front matter templates. The “square” ratio exists for the AI Model Matrix thumbnails and the “portrait” ratio for vertical social cards, but those are exceptions, not defaults. The P42 rule (PNG + WebP on the same deploy) assumes the landscape shape; the convert-to-WebP step is a single Pillow.save() call regardless of aspect ratio, but the cover templates that consume these files do not.
The ABS image-gen script pattern
The script that generated today’s covers is /tmp/gen_batch_N.py. The pattern is:
import os, sys, shutil
from pathlib import Path
for line in Path('/opt/data/.env').read_text().splitlines():
if line.startswith('OPENAI_API_KEY='):
os.environ['OPENAI_API_KEY'] = line.split('=', 1)[1]
break
sys.path.insert(0, '/opt/hermes')
from plugins.image_gen.openai import OpenAIImageGenProvider
from PIL import Image
LOCKED = (
"Bold graphic editorial illustration with a 1990s comic-book influence: "
"confident heavy ink outlines, dynamic composition, dramatic rim lighting, "
"subtle grunge/halftone texture. Palette: deep crimson red and electric "
"blue accents on a warm cream/beige background. "
)
BATCH = [{'key': 'g1', 'slug': 'guide-slug-2026-7-14', 'subject': '...'}, ...]
OUT = Path('/srv/abs-site/public/images')
provider = OpenAIImageGenProvider()
for entry in BATCH:
prompt = LOCKED + entry['subject'] + " Energetic but clean, professional editorial quality. No text. 16:9 landscape."
r = provider.generate(prompt=prompt, aspect_ratio="landscape")
if not r.get('success'):
print(f"! ERROR: {r}"); sys.exit(1)
src = r['image']
dst_png = OUT / f"{entry['slug']}.png"
shutil.copy(src, dst_png)
dst_webp = OUT / f"{entry['slug']}.webp"
Image.open(dst_png).save(dst_webp, 'WEBP', quality=82, method=4)
print('DONE')
Three rules for the script:
sys.path.insert(0, '/opt/hermes')before importing the plugin — the plugin package lives outside the venv.OUTLINE_STYLEprefix is canonical. The LOCKED paragraph is reused across batches; deviation breaks editorial consistency.- PNG + WebP on the same deploy. P42: both files land in
/srv/abs-site/public/images/in one pass.
What the openai-codex backend issue is
The openai-codex plugin is the ChatGPT-subscription OAuth path. As of 2026-07-14, the handshake fails at the OAuth step — the ChatGPT page returns a content-length mismatch or a 502. This isn’t an ABS bug; the OAuth provider doesn’t accept ABS’s user-agent pattern.
For the operator, this means:
- Don’t try to use
hermes chat --use-codex-authfor image gen on this VPS. - Don’t re-fork the broken path. The
openaiplugin is the working one. - A real ChatGPT Plus subscriber can re-export a ChatGPT-generated image via the API path as a manual workaround; this is hacky.
A codex-OAuth backend that just works again is unlikely soon; treat the openai plugin as the canonical path.
The deeper lesson here is about plugin-tree management. ABS keeps both plugins because the OAuth path will likely stabilize again — ChatGPT tends to fix the obvious breakage within a few weeks — and operators want the option to switch back when billing against a subscription becomes attractive again. But “available as an option” and “the path you should use today” are different things. The operator’s job is to keep the working path wired and not waste cycles on the broken one. If a future ABS release ships an OAuth fix, this guide will get a companion entry pointing operators at the new codex config; until then, ignore the codex directory and stay on the openai path.
Verification checklist
| # | Command | What it proves |
|---|---|---|
| 1 | `cat /opt/data/.env | grep OPENAI_API_KEY` |
| 2 | /opt/hermes/.venv/bin/python -c "import openai; print(openai.__version__)" | Package installed |
| 3 | Test the plugin via Python REPL | Endpoint reachable |
| 4 | Inspect a generated PNG with feh or xdg-open | Visual confirmation |
| 5 | Confirm WebP variant saved | P42 met |
Sources
- OpenAI API keys dashboard
- ABS live matrix: AI Model Matrix
- ABS companion: Image Gen Failover Chain in Priority Order
- ABS companion: WebP and PNG on the Same Deploy (P42)
Last verified: 2026-07-14 against the openai package version 2.33.0 in /opt/hermes/.venv/ and the openai plugin at /opt/hermes/plugins/image_gen/openai/init.py.



Submit a take
Have a different read on this? Drop a comment below — your email isn't published, and I read every one. Nothing leaves the site until I approve it.