pytest: fail CI when a configured image model id isn't in Sume's list
A pytest that reads your image model ids and checks each against GET /v1/images/models, so a retired id such as gpt-image-1 fails CI before it fails a customer.

Add one pytest that loads every model id your config file names and asserts each appears in the id list from GET /v1/images/models. A retired id fails CI the day you add it, and a catalog change that removes one fails the nightly run. It needs only an API key and takes one request.
This is a contract test for the id, not for output quality. It does not render anything, so it costs nothing.
The test
The catalog response is {"data": [{"id": ...}]}. The test accepts canonical ids and the legacy aliases Sume documents, so an old alias fails with a clear message to normalize it.
import json, os, urllib.request
import pytest
ALIAS_OK = {"gpt-image-2.5": "openai/gpt-image-2.5"}
def listed_ids():
req = urllib.request.Request(
"https://api.sume.com/v1/images/models",
headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]})
with urllib.request.urlopen(req, timeout=30) as r:
return {row["id"] for row in json.load(r)["data"]}
def configured_ids():
with open("image-models.json") as f:
return json.load(f)["ids"]
@pytest.mark.parametrize("model_id", configured_ids())
def test_model_id_is_listed(model_id):
ids = listed_ids()
canonical = ALIAS_OK.get(model_id, model_id)
assert canonical in ids, f"{model_id} is not in GET /v1/images/models"What it catches
| Id | Result | Reason |
|---|---|---|
| openai/gpt-image-2.5-sunburst | passes | Listed by Sume |
| gpt-image-1 | fails | Retired by OpenAI on 2026-10-23, not listed by Sume |
| gemini-2.5-flash-image | fails | Retired by Google on 2026-10-02, not listed by Sume |
| google/nano-banana-2 | passes | Listed by Sume |
Run it where it counts
Run the test on every pull request and on a nightly schedule, because the catalog can change without a commit in your repo. For a request-level check of parameters on top of the id, see the preflight post. For the same idea on the Sora side, see the mocked-Sora contract test.
Sources
Related posts
More in Developers
- Python asyncio semaphore: submit transcription jobs with a cap
Run Sume STT submits through asyncio.Semaphore and asyncio.to_thread, honor retry-after on 429, and keep one Idempotency-Key per clip. Tested code.
- A Python Enum for Sume job statuses and the terminal set
Sume jobs move through queued, processing, completed, failed and canceled. A tested Python Enum and terminal set so a poll loop stops on all three endings.
- Python exceptions for Sume errors: retry on the class, not the status
Map the Sume error envelope code to two exception classes, Retryable and Fatal, so one except clause drives retries. Covers 409, 429, 402 and 503.
- Python: which Sume video models take 20 seconds at 1080p?
A short Python script reads GET /v1/videos/models and filters by duration and resolution, so new launches never break a hard-coded model list.
Written by Sume