Build Customer-Facing Model Cards for Chinese AI APIs
Most teams start an AI gateway with a simple model list. The first version usually answers one question: which string do I put in the model field? That is enough for a prototype, but it is not enough for a production buyer, platform engineer, or support team.
A production model route has more than a name. It has a provider, a model version, a context window, an output ceiling, cache behavior, rate limits, tool charges, retention policy, and a dated rate card. If those details live only in a private spreadsheet, developers make routing decisions from stale screenshots and finance teams receive invoices they cannot reproduce.
The fix is a customer-facing model card endpoint.
This article shows how I would design one for an OpenAI-compatible gateway that serves Chinese model families such as DeepSeek, Kimi, GLM, and Qwen through a unified API like AIWave. The goal is not to advertise one universal winner. The goal is to make each route explain itself before an application sends traffic.
Source Snapshot for August 19, 2026
The model card should be generated from dated sources. For this article I checked the following public pages on August 19, 2026:
| Source | Current signal to expose |
|---|---|
| DeepSeek Models and Pricing | V4 Flash and V4 Pro list OpenAI and Anthropic base URLs, 1M context, 384K maximum output, cache-hit and cache-miss input rows, output rows, peak/off-peak windows, and concurrency limits. |
| Kimi K3 Pricing | Kimi K3 lists token billing per 1M tokens, cache-miss input at $3.00/M, cache-hit input at $0.30/M, and output at $15.00/M. |
| Z.AI Pricing | GLM-5.3, GLM-5.2, and GLM-5.1 list $1.40/M input, $0.26/M cached input, and $4.40/M output. |
| QwenCloud Qwen3.7 Plus and Qwen3.7 Flash | Qwen3.7 Plus and Flash expose 1M context, cache rows, token rates, tool support, TPM, and RPM. |
| AIWave Pricing | AIWave currently publishes all-day DeepSeek V4 Flash rates of $0.638/M input, $1.914/M output, and $0.0203/M cache hit; V4 Pro rates of $1.914/M input, $5.742/M output, and $0.0638/M cache hit. |
Do not turn this table into hardcoded product truth. Put retrieved_at, source_url, and rate_card_date into your model-card data so a future audit can see exactly what the API believed at the time.
What a Model Card Should Answer
A useful model card answers five operational questions.
First, what request shape is supported? Developers need to know whether the route accepts Chat Completions, Responses API, Anthropic format, images, tools, structured output, prefix completion, or batch jobs. A route that supports the same prompt text may still reject the same tool schema.
Second, what billing dimensions can change the invoice? DeepSeek separates cache-hit input, cache-miss input, output, and peak windows. Kimi K3 separates cache-hit input from cache-miss input and output. Qwen exposes context tiers and cache modes on model pages. GLM lists text model rows alongside vision, audio, image, video, tool, and agent categories. A single price_per_token field hides too much.
Third, what limits should an admission controller enforce? DeepSeek publishes concurrency limits for V4 Flash and V4 Pro. QwenCloud model pages publish TPM and RPM. Long-context routes also need maximum input, maximum output, and thinking-token rules. If the card omits limits, application teams discover them through production errors.
Fourth, what policy applies to logs and retention? A gateway should not place prompts, completions, real customer identifiers, API keys, or payment identifiers into public model metadata. The model card can expose retention class, region, and usage-ledger fields without leaking private data.
Fifth, how should a buyer compare routes? A buyer needs model version, source date, currency, cache behavior, and sample calculation method. They do not need unsupported benchmark claims.
A Minimal Model Card Schema
Start with a data structure that separates identity, capabilities, billing, and policy. Keep it small enough that docs, SDKs, and dashboards can consume the same JSON.
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True)
class PriceRow:
unit: str
input_usd: float | None
cached_input_usd: float | None
output_usd: float | None
notes: str
@dataclass(frozen=True)
class ModelCard:
provider: str
public_model: str
upstream_model: str
model_version: str
source_url: str
retrieved_at: datetime
context_tokens: int | None
max_output_tokens: int | None
supports_tools: bool
supports_json: bool
supports_vision: bool
price: PriceRow
policy_region: str
retention_class: str
The important detail is that the card can say "unknown" by using None. An unknown value is better than a guessed value. It forces a reviewer to decide whether the route is ready for public docs, internal preview, or temporary suppression.
Store Route Data as Dated JSON
In production I prefer a checked-in snapshot for docs plus a runtime refresh path for dashboards. The checked-in snapshot keeps SDK tests stable. The runtime path warns teams when a provider page changes.
{
"provider": "AIWave",
"public_model": "deepseek-v4-flash",
"upstream_model": "deepseek-v4-flash",
"model_version": "DeepSeek-V4-Flash-0731",
"source_url": "https://aiwave.live/pricing",
"retrieved_at": "2026-08-19T13:30:00Z",
"context_tokens": 1000000,
"max_output_tokens": 384000,
"supports_tools": true,
"supports_json": true,
"supports_vision": false,
"price": {
"unit": "1M tokens",
"input_usd": 0.638,
"cached_input_usd": 0.0203,
"output_usd": 1.914,
"notes": "AIWave all-day public rate card checked on 2026-08-19"
},
"policy_region": "Singapore",
"retention_class": "usage-metadata-only"
}
This format gives every downstream system the same vocabulary. Docs can render the card. SDKs can validate model names. Finance can join usage events to a dated rate. Support can see which policy was advertised when a customer opened a ticket.
Serve the Cards Without Exposing Secrets
The endpoint should be public, cacheable, and boring. It should never include internal keys, upstream credentials, private customer routes, negotiated discounts, or hidden fallback priorities. If an enterprise account has custom terms, expose those terms only in authenticated account settings.
Here is a small FastAPI example:
from datetime import datetime, timezone
from fastapi import FastAPI
app = FastAPI()
MODEL_CARDS = [
{
"provider": "AIWave",
"public_model": "deepseek-v4-flash",
"upstream_model": "deepseek-v4-flash",
"model_version": "DeepSeek-V4-Flash-0731",
"source_url": "https://aiwave.live/pricing",
"retrieved_at": "2026-08-19T13:30:00Z",
"context_tokens": 1000000,
"max_output_tokens": 384000,
"capabilities": {
"json": True,
"tools": True,
"vision": False,
"streaming": True
},
"pricing": {
"unit": "1M tokens",
"input_usd": 0.638,
"cached_input_usd": 0.0203,
"output_usd": 1.914
},
"policy": {
"region": "Singapore",
"retention_class": "usage-metadata-only"
}
}
]
@app.get("/v1/model-cards")
def list_model_cards():
return {
"generated_at": datetime.now(timezone.utc).isoformat(),
"cards": MODEL_CARDS,
}
This endpoint is intentionally separate from /v1/models. The model-list endpoint can remain compatible with OpenAI-style clients. The model-card endpoint can carry richer procurement and operations metadata without breaking SDK assumptions.
Add Validation Before Publishing
The most useful model-card system is the one that refuses bad cards before customers see them. A simple validation pass should catch missing source URLs, invalid dates, negative prices, unsupported public claims, and secret-looking strings.
import re
from urllib.parse import urlparse
SECRET_PATTERN = re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b")
def validate_card(card: dict) -> list[str]:
errors = []
url = card.get("source_url", "")
if urlparse(url).scheme not in {"https"}:
errors.append("source_url must be https")
if SECRET_PATTERN.search(str(card)):
errors.append("card contains a secret-looking token")
pricing = card.get("pricing", {})
for field in ("input_usd", "cached_input_usd", "output_usd"):
value = pricing.get(field)
if value is not None and value < 0:
errors.append(f"{field} cannot be negative")
if not card.get("retrieved_at"):
errors.append("retrieved_at is required")
if not card.get("public_model"):
errors.append("public_model is required")
return errors
Run this check in CI and again in the publishing script. The second check matters because model cards are often generated from provider docs or admin dashboards, not typed by hand.
Use Cards in the Client Experience
The model card should show up before a developer burns tokens. The docs page can render a compact table. The SDK can expose a client.model_cards.list() helper. The dashboard can attach a route explanation to each usage row.
A support workflow can also use the same data. When a customer asks why a request was blocked, support can answer with the public model card and the private admission event:
| Field | Public card | Private event |
|---|---|---|
| Model | deepseek-v4-flash |
deepseek-v4-flash |
| Context limit | 1M |
request had 1,120,000 input tokens |
| Max output | 384K |
request asked for 500,000 output tokens |
| Pricing date | 2026-08-19 |
usage event joined to same date |
| Policy | usage-metadata-only |
no prompt body stored |
This separation is useful. The card explains the rule. The private event explains the individual decision. Neither needs a real API key or prompt body.
Keep Pricing Math Reproducible
When pricing is cache-aware, the model card should not only display rows. It should describe how to calculate from usage. Here is a small cost function for a route with input, cached input, and output rows:
def estimate_cost_usd(input_tokens, cached_input_tokens, output_tokens, price):
billable_input = max(input_tokens - cached_input_tokens, 0)
return (
billable_input / 1_000_000 * price["input_usd"]
+ cached_input_tokens / 1_000_000 * price["cached_input_usd"]
+ output_tokens / 1_000_000 * price["output_usd"]
)
price = {
"input_usd": 0.638,
"cached_input_usd": 0.0203,
"output_usd": 1.914,
}
print(round(estimate_cost_usd(700_000, 280_000, 300_000, price), 6))
This is not an invoice. It is a pre-dispatch estimate that uses the same rows a reviewer can see. The actual invoice should come from the provider or gateway ledger, but the estimate makes surprises much rarer.
Operational Rollout Checklist
Ship model cards in stages.
Start with a public endpoint for stable routes only. Include source URLs, retrieval dates, model versions, capabilities, context, output limits, and pricing rows. Leave experimental routes out until they have enough metadata.
Next, wire the same card data into your SDK tests. If a model appears in /v1/models but lacks a card, fail the docs build or mark the route as internal preview.
Then, connect the usage ledger. Each request should store public_model, provider, rate_card_date, input_tokens, cached_input_tokens, output_tokens, status, and route_reason. Store metadata, not customer content.
Finally, add a change review. When a provider changes a model version, rate row, context limit, tool fee, or cache definition, create a new dated snapshot. Do not silently rewrite the old one. Historical usage needs historical terms.
Conclusion
OpenAI-compatible APIs reduce integration work, but they do not remove operational responsibility. A Chinese model gateway still has to explain which route is being used, what it supports, how it is billed, what policy applies, and which dated source backs the claim.
A customer-facing model card turns that explanation into an API contract. It helps Tier 1 and Tier 2 developers compare DeepSeek, Kimi, GLM, Qwen, and AIWave routes without depending on stale screenshots or private spreadsheets. More importantly, it gives support, finance, and platform teams the same evidence trail.
Before you publish the next model name, publish the card behind it.
Top comments (0)