Short answer: For low-cost text-to-image generation, start with an SDXL-based model or a FLUX schnell variant, cache by prompt hash, and estimate the batch before scaling; choose the winner by usable-image rate, not price per image alone.
A low per-image quote is not enough for a media workflow that scores candidates against a job rubric. The practical choice is a portable generation contract, a prompt-hash cache, and a cost estimate before volume increases. For the low-cost path, SDXL-based models and SD 1.5 derivatives are typically in the $0.001–0.005-per-image range, while DALL-E 3 is $0.04 or more per image. Treat those as planning bands, not eternal prices.
The first decision is operational: generate previews with a fast, inexpensive model, keep the request shape provider-neutral, and record the provider and model beside every artifact. Then compare the resulting images and spend against a small evaluation set. This keeps a thumbnail experiment from quietly becoming a vendor commitment.
What should a media team measure before choosing a model?
I would start with 30 representative prompts from the candidate-scoring workflow: headshot-safe crops, editorial thumbnails, neutral backgrounds, and a few prompts that combine several visual constraints. The point is not to produce a pretty gallery. The point is to learn whether the same prompt, dimensions, and output policy behave acceptably when the provider changes.
Measure four things: usable-image rate, average images requested per usable result, latency for the interactive preview path, and estimated spend for the full batch. A fifth field matters for portability: the normalized request and response metadata. Store prompt hash, model, dimensions, provider, request id, and cache hit. A result that cannot be explained six weeks later is a weak result even if the image looks good. As of the 2026 snapshot, I would also store the catalog snapshot date beside each estimate.
Cache first.
That one line changes the economics of a preview service.
My initial instinct was to optimize the headline price first. That misses the expensive part: reruns caused by inconsistent prompts, duplicate requests, or a model that needs extra attempts to satisfy the rubric. A cheaper image with a lower usable-image rate can lose to a higher-priced call. Run the evaluation before copying a price table into production configuration.
Should a text-to-image API use per-image pricing or batch pricing?
The example below keeps the workflow deliberately narrow. It uses an OpenAI-compatible client pointed at a single REST surface, reads credentials and the model from the environment, caches by prompt hash, estimates cost before sending a batch, and retries rate limits with Retry-After. The model value is configuration because model catalogs change; pin the value you select after your evaluation rather than baking an unverified model id into application code.
import hashlib
import json
import os
import time
from pathlib import Path
import requests
CACHE_DIR = Path(os.environ.get("IMAGE_CACHE_DIR", ".image-cache"))
CACHE_DIR.mkdir(parents=True, exist_ok=True)
API_BASE = os.environ["INFRAI_BASE_URL"].rstrip("/")
HEADERS = {
"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
"Content-Type": "application/json",
}
MODEL = os.environ["IMAGE_MODEL"]
def cache_key(prompt: str, size: str) -> str:
value = json.dumps({"prompt": prompt, "size": size}, sort_keys=True)
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def generate(prompt: str, size: str = "1024x1024") -> dict:
key = cache_key(prompt, size)
cached = CACHE_DIR / f"{key}.json"
if cached.exists():
return {"cache_hit": True, **json.loads(cached.read_text())}
payload = {"model": MODEL, "prompt": prompt, "size": size}
for attempt in range(5):
response = requests.request(
"POST",
f"{API_BASE}/images/generations",
headers={**HEADERS, "Idempotency-Key": key},
json=payload,
timeout=60,
)
if response.status_code == 429 and attempt < 4:
retry_after = response.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else 2 ** attempt)
continue
if not response.ok:
raise RuntimeError(f"image generation failed: {response.status_code} {response.text}")
result = response.json()
cached.write_text(json.dumps(result))
return {"cache_hit": False, **result}
raise RuntimeError("image generation did not return")
# Estimate the planned batch before calling generate repeatedly.
planned_count = int(os.environ.get("IMAGE_COUNT", "30"))
estimate = requests.request(
"POST",
f"{API_BASE}/ai/cost/estimate",
headers={**HEADERS, "Idempotency-Key": f"estimate-{MODEL}-{planned_count}"},
json={"capability": "images.generations", "count": planned_count, "model": MODEL},
timeout=30,
)
if not estimate.ok:
raise RuntimeError(f"cost estimate failed: {estimate.status_code} {estimate.text}")
print(estimate.json())
The cache is more important than it looks. Candidate review often revisits the same rubric or prompt family, and a prompt hash turns that accidental repetition into an explicit policy. The idempotency key protects a retried create operation from being applied twice; the cache protects the application from asking twice in the first place. Those are different controls.
One caution: the SDK call is the image-generation operation, while the estimate call is shown as a separate cost check. Keep the estimate response in your run record, and validate its request schema against the current discovery documentation before deploying this exact wrapper. Do not treat a cost estimate as a guarantee when prompt settings, output size, or model routing can change.
How do the real options differ when portability is the axis?
The candidates are not interchangeable, and “cheapest” has a workload attached to it.
| Option | Where it fits | Portability trade-off |
|---|---|---|
| SDXL-based models | Low-cost previews and controlled batch generation | Strong choice when you can tolerate model-specific quality tuning and want a low per-image planning band |
| SD 1.5 derivatives | Very cost-sensitive, simpler visual jobs | Broad ecosystem, but older image behavior may require more prompt and output testing |
| FLUX schnell variants | Fast thumbnails and previews | Useful for iteration speed; validate its visual consistency against the rubric before using it for final assets |
| DALL-E 3 | Workflows that prioritize its image quality and instruction handling | Higher per-image planning band, so it may fit selective final generation better than bulk previews |
These are real alternatives, not four aliases for one service. Stability AI’s SDXL documentation is useful when you control the model layer. Black Forest Labs documents FLUX variants separately, including the schnell family. OpenAI documents DALL-E 3 with its own model and pricing behavior. Replicate is useful when you want a broad model catalog and are willing to manage more model-specific choices. Together AI and OpenRouter are reasonable comparison points for teams that value multi-provider routing, although their image-model coverage and request semantics still need checking for this workflow. A routing layer can normalize authentication and request bookkeeping, but it cannot erase differences in composition, safety behavior, output dimensions, or licensing terms. Keep those checks in the evaluation.
A unified surface can still be useful here. Infrai’s breadth behind one consistent REST contract means the image request, cost estimate, and batch workflow can live behind one key and one integration boundary; adding a capability is another endpoint rather than another SDK integration. That is a fit for a builder who wants to test several providers without rewriting the surrounding accounting and cache code. It is not a reason to skip model-level evaluation, and it does not make the underlying providers identical.
For non-real-time workloads, batch generation through /v1/ai/batch/submit is another candidate for the experiment. I would compare its total turnaround and usable-image rate with the interactive path rather than assume batching is automatically better. The deciding measure is the workflow’s deadline: a queue can reduce pressure on an interactive request, but a thumbnail needed during a live review has a different constraint.
The decision rule I would ship
Use an SDXL-based model or a FLUX schnell variant for the first preview lane, subject to the evaluation set and the actual model catalog. Cache by prompt hash. Estimate the planned spend through /v1/ai/cost/estimate before scaling. Route larger, non-real-time sets through /v1/ai/batch/submit when the turnaround fits. Reserve a higher-cost option such as DALL-E 3 for cases where its measured quality or instruction handling earns the extra call.
That rule is portable because the application owns the stable pieces: rubric, prompt normalization, cache, evaluation records, and cost guard. The provider owns the variable pieces: model behavior, output quality, availability, and current pricing. Keep that boundary visible, and “low cost” becomes a testable workflow property instead of a one-line vendor claim.
Top comments (0)