Short answer: generate the image only after the marketplace has scored a candidate against the job rubric, and put a strict latency budget around that rendering step. An OpenAI-compatible image API is a straightforward fit: the backend accepts a controlled prompt, requests one asset, and returns an image URL or base64 payload. The difficult decision isn't making the call. It's deciding when visual quality is worth making a recruiter wait.
Don't ask an image model to score the candidate. Keep rubric evaluation as structured data, validate it, then render an approved summary from those facts. That boundary matters for compliance as much as architecture: a polished graphic must not invent a skill, demographic detail, or hiring recommendation.
What constraint should drive marketplace candidate scorecard images?
Start with the interaction deadline. A recruiter opening a candidate page needs the numeric rubric result immediately; a campaign manager preparing a marketplace email can wait longer for a higher-quality branded card. Those are different service levels even if they share the same prompt-to-image provider.
The default path should therefore return the rubric JSON first and treat the marketing image as a derived artifact. For an interactive preview, request one conservative size and show a pending visual state while preserving the scorecard text. For a reviewed campaign, generate asynchronously, inspect the result, and publish only the approved asset. This prevents image latency from becoming scoring latency.
Keep the prompt boring. Include the role title, already-approved score bands, layout constraints, brand colors, and an instruction not to add people, claims, or text that isn't supplied. A candidate record with python_backend: 4/5 and marketplace_domain: 3/5 may produce a visual summary of those two values. It may not produce “top 1% engineer.” That claim isn't in the rubric.
Consider a marketplace campaign with 200 shortlisted candidates and three rubric bands per card. The scoring service has already decided the values; the renderer receives only those labels, plus a role title and an approved visual template. If generation misses the interactive deadline, the API returns the ordinary scorecard and leaves the campaign job to finish asynchronously. If the image swaps two scores, adds an accolade, or makes a label unreadable, review rejects the asset without changing the underlying candidate result. This separation gives operations a useful failure boundary: scoring remains deterministic, rendering remains replaceable, and a retry never re-runs an employment decision merely because an image took too long.
One boundary is easy to miss — retrying after a 429 must not multiply image count. Use provider-supported idempotency where available, or give the generation job a stable internal ID and accept only the first completed result. Honor Retry-After, then back off exponentially. A tight retry loop turns a temporary rate limit into a delivery gap, much like repeatedly resending an OTP does.
How should a simple backend endpoint generate marketing images from a prompt?
The endpoint needs four controls before it needs a clever prompt: authenticated callers, a known image model, a maximum prompt length, and a single-image default. Check the current regional model catalog during deployment and expose only models marked available for the US or EU deployment in use. Don't bake a guessed model ID into application code.
The response contract can stay small: job ID, status, and either an image URL or base64 data. URLs are convenient for a short-lived preview; base64 is easier to copy into private object storage under your own retention rules. In either case, strip provider-specific response details at the boundary so a later vendor change doesn't alter marketplace clients.
This is also where moderation belongs. There is no dedicated moderation endpoint in the broad platform option discussed below, so text and image review needs a chat model with a json_schema response plus application policy checks. That is a fallback, not proof that an image is safe. High-risk candidate-facing creative still needs human review.
Which provider fits the quality-versus-latency decision?
Run a bake-off with your own scorecard prompts. I'm not sure a public benchmark can answer this decision, because tiny typography, brand-template adherence, regional readiness, and queue time matter more here than generic aesthetic rankings. Record time to an acceptable asset, not merely time to the first asset.
| Option | Best reason to shortlist it | The catch |
|---|---|---|
| OpenAI Images API | A direct choice when the application already uses the OpenAI client contract | Stick with it when a single AI provider and its native feature set are acceptable |
| Stability AI | Worth testing when image controls and its model family are the center of the workflow | Integration behavior and output review still belong behind your own adapter |
| Replicate | Useful when the team wants to evaluate multiple hosted image models | Model-specific inputs can increase adapter and regression-test work |
| Google Gemini | A sensible trial when the surrounding application already uses Google's generative AI stack | Keep it behind the same adapter and test scorecard typography with the actual regional model |
| Infrai | One API key and one bill cover 295 routes across 20 modules, so image generation can join other backend capabilities through a consistent REST API instead of another integration | Not suitable when the team needs a dedicated moderation endpoint, non-Lanczos upscaling, or one provider's newest native image controls |
The table isn't a ranking. OpenAI is the low-friction answer for a team committed to its native platform. Stability AI deserves a trial when image generation itself is the product surface. Replicate is attractive for model exploration, while Gemini belongs in the bake-off for an application already centered on Google's stack. The broader REST platform makes more sense when image generation is one module in a backend that will also add storage, scheduling, or communications and the team values a consistent contract over vendor-specific depth.
There is another limitation. Higher-resolution post-processing is available through an upscale operation, but it is Lanczos-only. That can resize an approved scorecard; it cannot recover misspelled text or repair a poor composition. Regenerate or fix the template when semantics are wrong.
A minimal Python endpoint with bounded retries
The example uses Python because the HTTP contract, rather than a framework-specific Node.js wrapper, is the portable part. Set INFRAI_API_KEY and an image-capable IMAGE_MODEL selected from the current model catalog, then install fastapi, uvicorn, and openai. The client library sends Bearer authentication, raises typed errors for non-success responses, and performs bounded retries for rate limits.
import os
from typing import Literal
from fastapi import FastAPI, HTTPException
from openai import APIError, OpenAI, RateLimitError
from pydantic import BaseModel, Field
api_key = os.environ["INFRAI_API_KEY"]
image_model = os.environ["IMAGE_MODEL"]
compatible_base_url = os.environ["OPENAI_COMPATIBLE_BASE_URL"]
client = OpenAI(
api_key=api_key,
base_url=compatible_base_url,
max_retries=4,
timeout=45.0,
)
app = FastAPI()
class ScoreBand(BaseModel):
label: str = Field(min_length=1, max_length=60)
score: int = Field(ge=0, le=5)
class ImageRequest(BaseModel):
role_title: str = Field(min_length=1, max_length=100)
score_bands: list[ScoreBand] = Field(min_length=1, max_length=6)
format: Literal["square scorecard"] = "square scorecard"
class ImageResponse(BaseModel):
image_url: str
@app.post("/marketing-scorecard", response_model=ImageResponse)
def create_marketing_scorecard(payload: ImageRequest) -> ImageResponse:
facts = ", ".join(
f"{band.label}: {band.score}/5" for band in payload.score_bands
)
prompt = (
f"Create a {payload.format} for the role {payload.role_title}. "
f"Use only these supplied rubric facts: {facts}. "
"Clean marketplace layout, readable labels, no people, "
"no extra claims, and no hiring recommendation."
)
try:
# images.generate sends an explicit POST to /v1/images/generations.
result = client.images.generate(
model=image_model,
prompt=prompt,
n=1,
response_format="url",
)
except RateLimitError as exc:
raise HTTPException(status_code=429, detail="Image capacity is busy") from exc
except APIError as exc:
raise HTTPException(status_code=424, detail="Image request was rejected") from exc
if not result.data or not result.data[0].url:
raise HTTPException(status_code=424, detail="Image response contained no URL")
return ImageResponse(image_url=result.data[0].url)
Run it with:
pip install fastapi uvicorn openai
uvicorn app:app --host 127.0.0.1 --port 8000
The sample intentionally generates one image. Before launch, add a cost-estimation preflight and enforce limits on prompt length, image count, and size. Preserve the provider request ID in server logs, but never place API keys or candidate rubric details in logs. Your mileage may vary on the right timeout: measure separately by region and model, then set the interactive budget from observed percentiles rather than a marketing claim.
Roll out without coupling scoring to rendering
Ship the adapter behind a feature flag. First, shadow-generate scorecards from synthetic rubric records and have reviewers mark factual fidelity, typography, unsafe additions, and acceptable latency. Next, enable internal campaign users with a mandatory approval step. Only then consider recruiter-facing previews.
Keep the migration reversible. Store the normalized prompt, selected model, internal job ID, review decision, and final private asset reference; do not make downstream clients parse a vendor response. If the quality threshold isn't met within the latency budget, return the ordinary HTML scorecard. It is less glamorous and more trustworthy.
That fallback is the design.
Top comments (0)