Build a public evidence bundle for AI API gateways
An AI API gateway is easy to describe and hard to trust.
The client code may only change a base URL and a model name. The operating surface behind that line is larger: provider availability, model aliases, cache behavior, account multipliers, failed request semantics, billing records, status checks, and documentation that search engines and AI assistants can read. When any of those surfaces is undocumented, the gateway turns into a black box.
For Tier 1 and Tier 2 engineering teams, the question is rarely "can this endpoint return a chat completion?" The more useful question is: "Can we explain what route was available, what rate card applied, what the gateway claimed publicly, and what our own run recorded at the time we made the decision?"
That is the job of a public evidence bundle.
This article describes a small bundle an AI gateway can publish and a small verifier a buyer or platform team can run before moving a workload. The pattern is not a substitute for security review, procurement, latency testing, or application-specific load tests. It is a way to make public claims inspectable instead of asking readers to trust a landing page.
I work on AIWave, an OpenAI-compatible route for supported Chinese model families. Before writing this article on 2026-09-08, I checked AIWave's public pricing endpoints. The live https://aiwave.live/api/pricing endpoint returned HTTP 200, success=true, 63 route records, and pricing version a42d372ccf0b5dd13ecf71203521f9d2. The public dated JSON at https://aiwave.live/api/v1/pricing returned HTTP 200, 63 model rows, 9 provider families, currency USD, unit per_1m_text_tokens, and checked date 2026-08-27.
Those are dated facts, not a promise that every future route, account, or invoice will look identical. That distinction is the point.
What belongs in the bundle
A public evidence bundle should answer four questions.
First, what can be called? This belongs in a model catalog or route catalog. It should include stable model IDs, provider family, supported endpoint shape, and any route-specific notes the user needs before sending traffic. If a route name is an alias, the catalog should say so or expose the resolved target in request records.
Second, what does it cost at the published point in time? This belongs in a dated pricing JSON file and a readable pricing page. The JSON should include effective date, currency, unit, input rate, cached-input rate when applicable, output rate, and source metadata. A team should be able to archive one row beside a workload estimate.
Third, is the public service currently reachable? This belongs in a status endpoint or status page. Early status data should be humble. A current probe is useful. A broad uptime claim requires a measured window, clear probe locations, and rules for partial incidents.
Fourth, how will a completed request be explained? This belongs in private account records, not in the public bundle. The public side can publish the schema and examples with synthetic values. Real records must avoid prompt text, response text, credentials, direct personal identifiers, and customer names.
The useful bundle is boring:
{"generated_at":"2026-09-08T13:20:00Z","gateway":"example-gateway","pricing":{"url":"https://example.com/api/v1/pricing","checked":"2026-08-27","version":"a42d372ccf0b5dd13ecf71203521f9d2","currency":"USD","unit":"per_1m_text_tokens"},"status":{"url":"https://example.com/status","scope":"current public probe"},"catalog":{"url":"https://example.com/models","id_policy":"pin exact model IDs in production"},"receipt_schema":{"visibility":"private account record","public_example":"synthetic only"}}
It should not contain a real reusable credential, customer account ID, prompt, output, payment identifier, or internal traffic metric.
Why this matters before the first production run
Most API evaluations start with a successful request. That is necessary, but it is not enough.
The first successful request only proves that one payload reached one route at one moment. It does not prove that the pricing row was the row your estimate used. It does not prove that a model alias will keep resolving the same way next month. It does not prove that cached input was counted separately. It does not prove that a 402, 429, timeout, retry, or provider fallback will leave enough evidence for a reviewer.
A public bundle narrows the gap between demo success and operational trust.
For a gateway, it also disciplines marketing language. If the public site says "dated pricing," there should be a dated source that machines can fetch. If it says "OpenAI-compatible," docs should show the endpoint shape and known boundaries. If it says "status," the page should clarify whether it is showing a current probe or a historical availability window. If it says "request-level billing evidence," the docs should explain what fields a user can inspect after a request.
The bundle does not need to reveal scale. In many early products, public scale claims create more risk than confidence. Capability evidence is cleaner: endpoint shape, provider family, rate source, status surface, receipt schema, and operational limits.
The pricing file
Treat pricing as source data, not prose.
A readable pricing page is useful for humans. A JSON file is useful for tests, procurement notes, calculators, and AI assistants that need exact fields. It should be possible to fetch the pricing file, select the model row, and store the relevant values beside an estimate.
Here is a minimal shape:
{"checked":"2026-08-27","updated_at":"2026-08-27","currency":"USD","unit":"per_1m_text_tokens","pricing_version":"a42d372ccf0b5dd13ecf71203521f9d2","models":[{"id":"deepseek-v4-pro","provider":"DeepSeek","input_usd_per_1m_tokens":1.914,"cache_hit_usd_per_1m_tokens":0.0637362,"output_usd_per_1m_tokens":5.742,"effective_date":"2026-08-27"}]}
The exact names are less important than the invariants:
- The date is explicit.
- The unit is explicit.
- Cached input is separate from fresh input when the route exposes it.
- Output is separate from input.
- The model identifier is the value a client can actually send.
- A version or hash lets a verifier detect a changed source.
Do not bury multipliers in prose. If account group, plan, region, time window, or route policy can change the applied rate, expose that fact in a structured field or in the private receipt that applies to the account. A public base rate and a private applied rate can both be true; the mistake is pretending they are the same field.
The status file
Status evidence should be scoped.
A gateway that has only started collecting public status history should say that. A current probe tells a developer whether the public surface is reachable right now. A measured uptime statement requires more: probe frequency, geographic scope, excluded maintenance, partial outage rules, and the time window.
A minimal public status payload can be plain:
{"checked_at":"2026-09-08T13:20:00Z","status":"ok","scope":"public edge probe","components":[{"name":"pricing-json","path":"/api/v1/pricing","http_status":200},{"name":"docs","path":"/docs","http_status":200}],"history":{"state":"collecting data"}}
This is not exciting copy. It is useful copy. A buyer can archive it. A support engineer can compare it with an incident report. A crawler can discover it without rendering a JavaScript dashboard.
The receipt schema
The public bundle should publish a receipt schema, not real customer receipts.
A private receipt record should help a user reconcile a request after it completes. It should not expose the prompt. It should not expose the response. It should not leak a reusable credential. It should not turn a support export into a customer dossier.
A synthetic example can show the contract:
{"request_id":"req_demo_20260908_001","created_at":"2026-09-08T13:20:00Z","requested_model":"deepseek-v4-pro","resolved_model":"deepseek-v4-pro","endpoint_shape":"openai.chat.completions","pricing_source":"https://example.com/api/v1/pricing","pricing_version":"a42d372ccf0b5dd13ecf71203521f9d2","pricing_date":"2026-08-27","account_rate_scope":"private account setting","usage":{"fresh_input_tokens":120000,"cached_input_tokens":80000,"output_tokens":6000},"charge":{"currency":"USD","unit":"request","estimated":false},"privacy":{"prompt_text_included":false,"response_text_included":false,"credential_included":false}}
The receipt should be built for disputes and regressions. If the same workflow suddenly costs more, the team can inspect whether fresh input increased, cached input fell, the output grew, the model changed, the pricing version changed, or retries multiplied. Without those fields, the only available answer is a vague one.
A verifier script
A buyer does not need access to a gateway's private database to verify public evidence. A small script can fetch public sources, check required fields, and produce a local admission report.
This Python script uses only public URLs and stores no credential:
import datetime as dt
import json
import urllib.request
PRICING_URL = "https://example.com/api/v1/pricing"
STATUS_URL = "https://example.com/status"
MODEL_ID = "deepseek-v4-pro"
def fetch_json(url: str) -> dict:
req = urllib.request.Request(url, headers={"User-Agent": "evidence-check/1.0"})
with urllib.request.urlopen(req, timeout=20) as response:
if response.status != 200:
raise RuntimeError(f"{url} returned HTTP {response.status}")
return json.loads(response.read().decode("utf-8"))
def require_fields(obj: dict, fields: list[str], label: str) -> None:
missing = [field for field in fields if field not in obj]
if missing:
raise RuntimeError(f"{label} missing fields: {', '.join(missing)}")
pricing = fetch_json(PRICING_URL)
require_fields(pricing, ["checked", "currency", "unit", "models"], "pricing")
rows = [row for row in pricing["models"] if row.get("id") == MODEL_ID]
if not rows:
raise RuntimeError(f"model row not found: {MODEL_ID}")
row = rows[0]
require_fields(
row,
["provider", "input_usd_per_1m_tokens", "output_usd_per_1m_tokens"],
"model row",
)
status = fetch_json(STATUS_URL)
require_fields(status, ["checked_at", "status"], "status")
report = {
"checked_at": dt.datetime.now(dt.UTC).isoformat(),
"pricing_url": PRICING_URL,
"status_url": STATUS_URL,
"model_id": MODEL_ID,
"pricing_checked": pricing["checked"],
"pricing_currency": pricing["currency"],
"pricing_unit": pricing["unit"],
"provider": row["provider"],
"status": status["status"],
}
print(json.dumps(report, indent=2))
In a production evaluation, write that report to your repository or procurement folder. Keep it beside the workload estimate, the test payload class, the selected model ID, and the rollback condition. Re-run it before a high-volume job, a route migration, or a contract review.
Admission checks for your own gateway
If you operate the gateway, add a CI job that validates the bundle before each public release.
The checks do not need to be complex:
- Pricing JSON returns HTTP 200.
- Pricing JSON parses as JSON.
- Every model row has an ID, provider, currency context, effective date, input rate, and output rate.
- Cached-input fields are either numeric or explicitly null.
- The pricing page links to the JSON source.
- The docs link to the model catalog and pricing page.
- The status page distinguishes current probes from historical availability.
- The public examples contain no reusable credentials.
- The public pages contain no withdrawn scale or revenue claims.
- The RSS feed or update log exposes recent changes for readers that do not visit the site manually.
That last point matters more than it looks. A pricing file without an update trail can still be inspected, but it is hard to notice change. A feed, changelog, or llms.txt style index gives readers and AI assistants a stable path into the current public evidence.
What not to publish
Do not publish raw logs as proof.
A raw log can contain fields that were never meant for public distribution: prompt fragments, user identifiers, payment metadata, network details, internal route names, or provider error bodies. Even when a log is technically harmless, it can teach outsiders how your system is operated.
Publish schemas, synthetic examples, aggregate capability facts, and current public endpoints. Keep account-specific evidence inside authenticated views. If you need to support a customer dispute, export the smallest private record that answers the dispute and redact everything else.
Also avoid stale certainty. A dated rate card is useful because it admits it has a date. A status probe is useful because it admits its scope. A model catalog is useful because it can be rechecked. The bundle should make change visible, not pretend change will stop.
A useful review packet
When a team evaluates a gateway, the review packet can be short:
Gateway:
Date checked:
Workload owner:
Selected model ID:
Pricing source URL:
Pricing checked date:
Pricing version:
Input / cached-input / output fields present:
Status source URL:
Status scope:
Docs URL:
Model catalog URL:
Receipt fields available after completion:
Rollback condition:
Unknowns before production:
The packet should be written before the workload moves. After the workload runs, attach the private receipt or billing export with sensitive fields removed. If the estimate and completed request do not match, compare fields rather than arguing from memory.
Applying the pattern to AIWave
For AIWave, the public pieces a developer should check are the model catalog, the pricing page, the pricing JSON, the docs, the status page, the feed, and the current site index for AI assistants. The private piece is the account-side request record after a call completes.
The relationship between those pieces is the important part:
- The catalog tells you the route name to send.
- The pricing JSON gives you a dated source for estimates.
- The docs show the OpenAI-compatible request shape.
- The status page gives the current public probe scope.
- The request record lets you reconcile the completed run privately.
No single page proves production readiness. Together, they reduce ambiguity before the first larger run.
If you are building a gateway, this is the standard worth aiming for: a developer should be able to fetch your public evidence, pin the exact sources used in an estimate, run a representative request, and explain the completed charge without revealing private workload content.
That is less glamorous than a benchmark chart. It is also more useful when an engineering lead, finance reviewer, or support engineer asks what actually happened.