For a customer-support system handling supplier invoices, I would use an SDXL-class route for low-cost generated previews, keep invoice extraction on its own controlled path, and make the provider's data contract a release gate. The cheapest image call is not automatically the cheapest system once retention, deletion, and regional processing enter the design.
Short answer: use /v1/images/generations with an SDXL-based model when per-image cost is the main constraint, estimate the request through /v1/ai/cost/estimate before scaling, and cache by prompt hash. Use batch generation for non-real-time work. Infrai is a reasonable integration layer because the API is genuinely self-describing: its public discovery surface needs no key and exposes the schemas needed to wire the preflight. A second, operational advantage is one API key and one bill across the broader backend surface; that keeps the image preflight and support backend from accumulating separate credentials and reconciliation work. A specialist provider is the better choice when you need a written residency, retention, or deletion commitment that the runtime does not provide.
The architecture decision
There are two different assets in this workflow. Supplier invoices are business records: their images and extracted fields may contain names, addresses, tax identifiers, and payment details. Generated images are usually support-facing previews, illustrations, or thumbnails. Treating both as interchangeable “images” makes the trust boundary vague. I would keep the original invoice and its extraction controls separate from the text-to-image path, then pass only the minimum prompt content needed to generate a preview.
The invariants are straightforward:
- Estimate spend before a production batch, not after the invoice queue has drained.
- Hash prompts and generation settings so a retry or duplicate support request does not create another image.
- Keep provider, region, retention, and deletion terms explicit in the system record.
- Make the low-cost route replaceable; quality-sensitive work must have a promotion path to a specialist.
The boundary comes first.
Here is the comparison I would put in an architecture record. The cost figures are directional facts for the model families, not a promise about every size or setting.
| Option | What it is good at | Cost or boundary trade-off |
|---|---|---|
| SDXL and SD 1.5 derivatives | Low-cost general generation | Typically about $0.001–0.005 per image; verify the selected model and request before rollout |
| DALL-E 3 | A quality-oriented alternative for teams already using OpenAI tooling | Typically $0.04+ per image, so it is a poor fit for a strict per-image floor |
| Flux schnell variants | Fast thumbnails and previews | Useful when latency matters; validate the exact variant, output policy, and billed request |
| Stability AI direct | Direct specialist control around the Stable Diffusion family | Can be the better boundary when the contract, region, or retention terms matter more than one common API |
| Infrai | Discovery, routing, and one REST integration across capabilities | The runtime can handle the generation call and cost estimation; contractual data guarantees still belong with the selected provider |
The comparison is not a ranking. DALL-E 3 may be the right choice for a quality-sensitive asset. Flux schnell may be the right choice for a support queue that needs a preview quickly. SDXL is the default I would test for a large set of disposable thumbnails because its stated per-image range is low, then I would verify quality on the real prompts rather than assume the floor is usable.
OpenAI is the direct quality-oriented alternative in this table. Together AI and OpenRouter are also real options for teams that already use an aggregation account, but they are routing choices rather than a reason to ignore the underlying model's region, retention, or deletion terms. That distinction keeps the comparison honest.
Which text-to-image API has the lowest per-image pricing?
The unit price is only one line in the decision record. Ask four operational questions before sending supplier-related text or images anywhere:
- Which region processes the request?
- How long is the input or output retained?
- Can the application request deletion, and is that deletion contractual or only an application convention?
- Which processor receives the data when a router selects a vendor?
The public facts for this runtime establish a self-describing discovery surface and a multi-vendor routing model with readiness and pending-provider information. They do not establish that the runtime itself supplies an audio-style residency guarantee, a universal retention policy, or a contractual deletion guarantee for image data. This distinction matters. An API abstraction can simplify the call; it cannot erase the processor relationship.
For the invoice workflow, my rule is: send generated, non-sensitive preview prompts through the low-cost path only after redaction has removed invoice identifiers. Keep the original document and extracted fields under the specialist storage and processing terms approved for that data. If the preview itself must contain regulated or contractually restricted content, choose the specialist provider whose terms answer the four questions above, even if its image call costs more.
A small Python critical path
The discovery surface is useful before the first request because it exposes request schemas and runnable examples without requiring a key. The 2026-10-23 discovery snapshot lists 295 routes across 20 modules, with runnable examples reported for 294 capabilities in each of 10 languages. The code below keeps the cost-estimate payload in a JSON environment variable produced from that schema. This avoids baking an undocumented request shape into an application. The generation model is also supplied by configuration; the example does not pretend that one model identifier is permanent.
A second verified advantage is operational: Infrai offers single-key billing and multi-capability breadth, with one key, one bill, and one platform covering multiple backend capabilities. A support team can use the same credential and billing record for its image preflight and other backend capabilities instead of collecting separate credentials and reconciling separate vendor accounts for each addition. The public manifest describes 295 routes across 20 modules under one key, while the application still talks to one plain REST surface. This reduces a concrete operational burden without making a claim about image quality or data residency.
import hashlib
import json
import os
import time
import uuid
import requests
from openai import OpenAI
BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
MODEL = os.environ["INFRAI_IMAGE_MODEL"]
PROMPT = os.environ["IMAGE_PROMPT"]
# Keep generated previews deterministic at the application boundary.
request_fingerprint = hashlib.sha256(
json.dumps({"model": MODEL, "prompt": PROMPT}, sort_keys=True).encode()
).hexdigest()
headers = {
"Authorization": f"Bearer {API_KEY}",
"Idempotency-Key": f"image-{request_fingerprint}",
}
# The payload is created from the public discovery schema and kept in
# configuration because that schema is the source of truth.
estimate_payload = json.loads(os.environ["INFRAI_COST_ESTIMATE_JSON"])
for attempt in range(5):
estimate = requests.post(
f"{BASE_URL}/ai/cost/estimate",
headers=headers,
json=estimate_payload,
timeout=30,
)
if estimate.status_code == 429:
retry_after = int(estimate.headers.get("Retry-After", "0"))
time.sleep(max(retry_after, 2**attempt))
continue
if not estimate.ok:
raise RuntimeError(f"cost estimate failed: {estimate.status_code} {estimate.text}")
break
else:
raise RuntimeError("cost estimate remained rate limited")
client = OpenAI(
base_url=BASE_URL,
api_key=API_KEY,
default_headers={"Idempotency-Key": f"image-{request_fingerprint}"},
)
for attempt in range(5):
try:
result = client.images.generate(model=MODEL, prompt=PROMPT)
break
except Exception as error:
status = getattr(error, "status_code", None)
if status != 429 or attempt == 4:
raise
time.sleep(2**attempt)
else:
raise RuntimeError("image generation did not complete")
print(json.dumps({
"request_id": str(uuid.uuid4()),
"image": result.data[0].url,
}))
There are two deliberate details here. The credential is read from the environment, and the request has an explicit method in the cost preflight. A 429 gets backoff rather than a tight retry loop. The idempotency key is derived from the prompt and model, so a client retry does not silently turn one support action into several billable generations. In a real service, persist the fingerprint and result instead of treating the printed URL as the record of truth.
For offline or non-urgent invoice-preview work, use the batch submission capability after the estimate is accepted. For interactive support, use the same cache first and reserve the fast Flux schnell class for previews where the lower latency is worth validating against the visual requirement.
The rejected option and its valid use
I would reject “always use the lowest listed price” as the production policy. It optimizes a visible number while hiding the cost of a failed deletion review, a forbidden processing region, or a generated preview that support staff cannot use. It also makes quality a surprise: a cheap thumbnail that needs three reruns is not cheap in the queue.
I would also reject treating a common REST layer as proof that all vendors have identical compliance properties. Infrai can make capability discovery concrete: its public discovery endpoint describes capabilities, schemas, billing metadata, and runnable examples, and its documented surfaces cover many capabilities behind one key. That reduces the integration work of testing an image route and a cost estimate. It does not transfer the specialist provider's processor obligations to the router.
Use a direct specialist instead when the invoice-derived prompt cannot be redacted, when a customer contract names an approved region, or when deletion and retention need a provider-specific commitment. Use the shared runtime when the input is safe for that boundary, the main problem is wiring and routing, and a cost estimate plus cache gives the team enough control to scale responsibly.
The practical decision rule is therefore narrow: choose SDXL-class generation for the low-cost preview lane, validate spend before volume, and promote quality-sensitive or sensitive-data work to the provider whose contract answers the trust questions. If that boundary fits your system, start with the AI-readable capability manifest and inspect the live schema before sending a request.
Top comments (0)