guide · computers

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.

July 14, 2026 · By Alastair Fraser

A friendly retro robot at an image-gen console with a small OPENAI API KEY keychain, plugging it into a poster PRINTER labeled GPT-IMAGE-2-MEDIUM. Three printed posters hang drying on a rack: each printed PNG has a small WebP twin beside it. A STOP sign blocks a separate broken console labeled OPENAI-CODEX OAUTH.

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/:

  1. openai/ — Python openai package, real OPENAI_API_KEY, working today.
  2. 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:

  1. sys.path.insert(0, '/opt/hermes') before importing the plugin — the plugin package lives outside the venv.
  2. OUTLINE_STYLE prefix is canonical. The LOCKED paragraph is reused across batches; deviation breaks editorial consistency.
  3. 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-auth for image gen on this VPS.
  • Don’t re-fork the broken path. The openai plugin 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

#CommandWhat it proves
1`cat /opt/data/.envgrep OPENAI_API_KEY`
2/opt/hermes/.venv/bin/python -c "import openai; print(openai.__version__)"Package installed
3Test the plugin via Python REPLEndpoint reachable
4Inspect a generated PNG with feh or xdg-openVisual confirmation
5Confirm WebP variant savedP42 met

Sources

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.

Sources

#image-gen#openai#api-key#plugin#working

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.

Your email address will not be published. Required fields are marked.