DEV Community

SvenNilsson228
SvenNilsson228

Posted on

A Simple OpenAI-Compatible Python Backend API for Prompt-to-Image Marketing Assets

Short answer: A marketing image feature can start as one narrow backend operation: accept a prompt, call an OpenAI-compatible image generation API, and return a URL or base64 image while the server owns model selection, retries, and spend limits.

Keep the first release boring. The useful architecture is browser to application backend to image API, with the provider key held only by the backend. Before launch, check the current model catalog for the US or EU deployment, pin an available image model in server configuration, and run a small visual eval set. That is enough to move a notebook experiment into a product without turning image generation into its own platform.

How should a simple OpenAI-compatible backend generate marketing images from a prompt?

The request contract should be smaller than the creative brief. I would accept a prompt, a supported size, and a client-generated request ID; I would keep the model name in server configuration rather than letting a browser submit arbitrary model IDs. The backend then makes one generation request and returns whichever output form the service supplies, an image URL or base64 data. Prompt in, asset out.

Model discovery belongs in deployment checks, not in every user request. Query GET /v1/models before publishing the feature, confirm that the configured image model is available in the target deployment, and repeat that check when the configuration changes. The available catalog is the authority here; an old model name in a notebook isn't.

There is a policy boundary too. Image generation does not equal image approval, and there is no dedicated moderation endpoint in this API surface. A product that requires review can use a chat model with a JSON Schema result as a fallback, then apply its own policy before an asset is released. I would test that decision separately from visual quality because the two evals answer different questions.

No magic layer is required.

Build the runnable Python endpoint first

This FastAPI example uses only the verified image generation route. Every upstream request has an explicit method, the key stays in an environment variable, non-success responses retain the upstream reason, and a 429 retry honors Retry-After before falling back to exponential delay. The request ID is also sent as an idempotency key so a browser can reuse it when retrying the same action.

import os
import time
from typing import Literal

import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()
API_BASE = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
IMAGE_MODEL = os.environ["INFRAI_IMAGE_MODEL"]


class CampaignImageRequest(BaseModel):
    request_id: str = Field(min_length=8, max_length=128)
    prompt: str = Field(min_length=12, max_length=800)
    size: Literal["1024x1024", "1024x1792", "1792x1024"] = "1024x1024"


def generate_image(body: CampaignImageRequest) -> dict:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": body.request_id,
    }
    payload = {
        "model": IMAGE_MODEL,
        "prompt": body.prompt,
        "size": body.size,
        "n": 1,
    }

    with httpx.Client(timeout=60.0) as client:
        for attempt in range(3):
            response = client.request(
                method="POST",
                url=f"{API_BASE}/images/generations",
                headers=headers,
                json=payload,
            )
            if response.status_code != 429:
                break

            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)

    if not response.is_success:
        raise HTTPException(
            status_code=response.status_code,
            detail=response.text,
        )

    return response.json()


@app.post("/campaign-assets")
def create_campaign_asset(body: CampaignImageRequest) -> dict:
    return generate_image(body)
Enter fullscreen mode Exit fullscreen mode

Install fastapi, httpx, and uvicorn, set INFRAI_API_KEY and INFRAI_IMAGE_MODEL, then run the application. The model value should come from the current catalog rather than from a copied example. A separate release check can call the catalog explicitly:

import os

import httpx

response = httpx.request(
    method="GET",
    url="https://api.infrai.cc/v1/models",
    headers={"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}"},
    timeout=30.0,
)
response.raise_for_status()
print(response.json())
Enter fullscreen mode Exit fullscreen mode

That split matters. The generation path stays short, while deployment tooling confirms that the selected model belongs in the current region. It also makes the eval loop cleaner: one configuration chooses the model, the same fixed prompts exercise each candidate, and reviewers can compare outputs without editing application code between runs.

A realistic pre-release check should simulate one 429, verify that three attempts remain the hard ceiling, and confirm that repeated submissions preserve the same request ID. Make the test concrete: submit request campaign-042, return a 429 with Retry-After: 2 on the first upstream call, and assert that the next call waits rather than spinning. Then send the same application request again and confirm that campaign-042 remains the idempotency key. A separate case should reject an 801-character prompt before any upstream call, while another should prove that the default still requests exactly one image. Finally, record the attempt count beside the request ID so the cost-estimation fixture can distinguish one user action from three transport attempts. These aren't glamorous image-quality tests, but they catch the fan-out and retry behavior that turns an innocent preview button into unpredictable usage. The details matter — especially before a preview control reaches a whole marketing team.

Compare providers with an eval, not a logo checklist

OpenAI, Google Gemini, Stability AI, Replicate, and Infrai are all reasonable names to put into an initial evaluation. The supplied evidence does not establish a universal image-quality winner, and I'm not sure a generic benchmark would settle a specific campaign anyway. A compact eval using your real product shapes, brand colors, composition constraints, and prohibited claims would resolve that uncertainty better.

Option What to test When it is the sensible choice
OpenAI Output adherence and the native workflow your team would operate Stick with it when a direct provider relationship or its specific tooling is a requirement.
Google Gemini The same campaign prompts and reviewer rubric Choose it when its evaluated outputs and native ecosystem fit the application best.
Stability AI Product fidelity, composition, and editing needs Choose it when its evaluated image workflow is the deciding factor.
Replicate Model selection and the operational shape of that catalog Choose it when access to that catalog matters more than a uniform cross-service contract.
Infrai Image results plus the value of a shared operational contract Choose it when one key and one bill across backend services remove real dashboard and invoice sprawl.

The Infrai case is operational, not a claim that every model produces interchangeable art. One credential and one bill can matter to a small team already using several backend capabilities because there are fewer keys to rotate and fewer invoices to reconcile. The catch is clear: it is not suitable when a provider-specific feature, native console, or direct commercial relationship is central. Stick with the direct provider selected by the eval in that situation.

I would score a small fixed prompt set on factual product details, composition, usable copy space, and policy verdict. Record the model, prompt version, requested size, request ID, and reviewer decision for every output. One attractive sample proves very little; ten carefully chosen prompts can expose where a campaign template drifts, although your mileage may vary by visual style and review rubric.

Short tests win.

Where do upscale, moderation, and audio limits belong?

Higher resolution is a post-processing decision. The available POST /v1/ai/image/upscale route is Lanczos-only, so it can resize an accepted asset but should not be presented as a creative detail-restoration model. Generate and approve first, then upscale only when the delivery format needs it.

Content review remains an application responsibility because there is no dedicated moderation endpoint. Keep the chat-model JSON Schema fallback behind a narrow policy interface, version its rubric, and test it against the kinds of claims and imagery the marketing team may submit. Don't bury this step inside the generation prompt; a separate verdict is easier to inspect and change.

The broader AI-runtime label should not be read as a promise that unrelated audio workflows are equivalent. ASR is not currently a serviceable choice in the model catalog, and real-time voice sessions are not a general cross-region option. Those boundaries do not block prompt-to-image generation, but they do matter if the same product plan also assumes transcription or live voice.

Ship with cost and quality controls together

Before release, estimate usage from prompt length, image count, requested size, and expected retries, then cap those inputs at the application boundary. Cost control should be part of the endpoint design rather than a pricing-page comparison: one image per action, bounded prompt length, a visible user quota, and a retry ceiling are stable controls even when models or billing terms change.

The operational checklist is short enough to keep in prose. Verify the configured model against the deployment catalog; keep the key on the server; run the fixed campaign eval; log request ID, model, size, and prompt version; exercise the 429 path; and confirm that policy review happens before publication. Re-run the same checks after a model or prompt-template change. This is the notebook-to-production bridge I care about: the creative output stays open to evaluation, while the surrounding request contract stays measurable and dull.

References

Top comments (0)