API Reference
One gateway, two modes. OpenAI-compatible request shape, Qianfan models behind it.
https://api.tokencoup.com is the planned production endpoint and is not live yet. The reference server runs locally at http://127.0.0.1:8081 (mock mode by default), and the international resale key pool is not connected — Mode B returns resale_pool_empty until a real international Qianfan key is added. Everything below documents the contract that is already shipped and tested. v0.3 adds GET /v1/usage, GET /v1/models and buffered SSE streaming; all three are covered by the reference server's test suite.Overview
The tokencoup gateway is a thin, OpenAI-compatible proxy in front of Baidu Qianfan. It adds gateway-level authentication, rate limiting, usage metering and billing without rewriting your request body.
The edition and the mode are encoded in the URL path, not in headers. That makes the data-residency boundary explicit and prevents a domestic key from ever being used to serve an overseas request (C2/S3 guard).
| Edition | Path prefix | Modes | Upstream base | Data residency |
|---|---|---|---|---|
International (intl) | /intl/a/…, /intl/b/… | A + B | https://api.baiduqianfan.ai/v1/ | Overseas — S1 target; actual data residency pending written confirmation from Baidu |
Domestic (cn) | /cn/a/… | A only | https://qianfan.baidubce.com/v2/ | Mainland China |
{edition}/{mode}/ prefix is appended to that edition's upstream base. Bases were verified against official Qianfan docs on 2026-11-12: https://api.baiduqianfan.ai/v1/ (intl) and https://qianfan.baidubce.com/v2/ (cn). The intl edition's overseas data-residency claim still requires written confirmation from Baidu; see the Cross-border page before relying on it.Quickstart
- Create a gateway API key (
rf_…). Its plan sets your RPM and token quota. - Choose Mode A (bring your own Qianfan key) — available on both editions.
- POST an OpenAI-style chat payload to
/intl/a/….
BASE=https://api.tokencoup.com
curl -s "$BASE/intl/a/v1/chat/completions" \
-H "X-Gateway-Key: rf_YOUR_GATEWAY_KEY" \
-H "Authorization: Bearer YOUR_QIANFAN_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "ernie-4.0-8k",
"messages": [{"role": "user", "content": "Say hi in one sentence."}]
}'{
"id": "chatcmpl-…",
"object": "chat.completion",
"model": "ernie-4.0-8k",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Hi there!"},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 12, "completion_tokens": 5, "total_tokens": 17}
}Authentication
There are two distinct keys. Confusing them is the most common integration error.
| Header | Applies to | Value | Handling |
|---|---|---|---|
X-Gateway-Key | All requests | rf_… gateway API key | SHA-256 hash stored in the gateway DB; linked to a plan and subscription |
Authorization: Bearer … | Mode A only | Your own Qianfan key | Not stored; forwarded to the upstream |
X-Provider-Key | Mode A only | Alternative to Authorization | Not stored; forwarded to the upstream |
Content-Type | All requests | application/json | Required for JSON bodies |
X-Gateway-Key returns 401 gateway_auth_failed before any upstream call. The plaintext key is shown only once at creation./intl/… — or an international key on /cn/… — is rejected with 403 key_edition_mismatch. Any key not explicitly registered in tokencoup's edition registry fails closed with 403 key_edition_unknown. Key prefixes are deliberately ignored: domestic and international Qianfan keys share the bce-v3/ALTAK- prefix, so a prefix cannot prove an edition.Endpoints
POST is used for inference: the first two path segments select edition and mode, and the remainder is forwarded verbatim to the Qianfan upstream. GET covers two read-only endpoints: /v1/usage (local ledger) and /v1/models (upstream passthrough). GET calls are authenticated and rate-limited, but never consume tokens or incur billing.
| Method | Path | Mode | Meaning |
|---|---|---|---|
| POST | /intl/a/<qianfan-path> | A | Bring-your-own-key relay — all editions |
| POST | /intl/b/<qianfan-path> | B | Metered resale — international edition only |
| GET | /intl/{a|b}/v1/usage | A/B | Measured local usage — no upstream call, no billing |
| GET | /intl/{a|b}/v1/models | A/B | verified public snapshot (17 models, 14 priced) |
{
"model": "ernie-4.0-8k",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Summarize this in two lines: …"}
],
"temperature": 0.7,
"stream": false
}model. Treat any path below the prefix as Qianfan's own API surface (for example v1/chat/completions).Usage (GET, local truth)
GET /intl/{a|b}/v1/usage returns measured values from the gateway's own subscriptions and usage_ledger tables. It makes no upstream call, is not metered and is not billed. Quota and RPM come from gateway/plans.py — the same source injected into the pricing page at build time.
curl -s http://127.0.0.1:8081/intl/a/v1/usage \
-H 'X-Gateway-Key: rf_...'| Field | Meaning |
|---|---|
token_quota / rpm_limit | Plan values from gateway/plans.py |
token_used_billable | Mode B billable tokens consumed this period |
token_remaining | quota − token_used_billable |
ledger_calls | All recorded calls (Mode A and Mode B) |
ledger_tokens | Estimated tokens across all recorded calls |
billed_amount_usd | Sum of billed_amount in the ledger |
overage_usd_per_1k_tokens | Derived from plans.TOKEN_PRICE_USD |
len(body)/4) until the upstream usage field is parsed. token_used_billable and ledger_tokens have different scopes: only billable Mode B calls increment the former. Neither is a final invoice — see Billing & quotas.Model list (GET, passthrough)
GET /intl/{a|b}/v1/models is relayed to the Qianfan upstream under the same edition/key checks (C2 S3) as inference. Verified 2026-11-12: the public endpoint https://api.baiduqianfan.ai/v1/models returned HTTP 200 with 17 models, 14 carrying published per-token prices, with no authentication required. tokencoup does not rewrite that catalogue — the homepage model price table is a dated snapshot produced by site/fetch_models.py. Mock mode makes no network call and returns an empty list.
curl -s http://127.0.0.1:8081/intl/a/v1/models \
-H 'X-Gateway-Key: rf_...' -H 'Authorization: Bearer <your-qianfan-key>'datacenters report country_code: CN. This is upstream, first-party counterevidence to any claim that international traffic never enters mainland China. Directory prices are not a settlement commitment — confirm model IDs, prices and actual residency in the Qianfan console and in writing before launch. Models using a nested pricing schema (schema_version 2.x) are listed but not priced on the homepage.Streaming (SSE)
Set stream: true in the request body. The response uses Content-Type: text/event-stream with OpenAI-style data: chunks terminated by data: [DONE]. v0.3 uses buffered passthrough: the upstream SSE body is read in full and written once. Chunk-by-chunk flushing is not implemented yet, so clients must not assume token-by-token latency.
curl -N http://127.0.0.1:8081/intl/a/v1/chat/completions \
-H 'X-Gateway-Key: rf_...' -H 'Authorization: Bearer <your-qianfan-key>' \
-H 'Content-Type: application/json' \
-d '{"model":"ernie-4.0-8k","messages":[{"role":"user","content":"hi"}],"stream":true}'data: {"id":"mock-chunk-1","object":"chat.completion.chunk","mock":true,"choices":[...]}
data: [DONE][DONE] so SDK clients can be wired up without a provider key.Modes: BYOK vs metered
| Mode A — BYOK | Mode B — metered resale | |
|---|---|---|
| Provider key | You supply it | tokencoup's international key pool |
| Billing | Flat subscription, no token markup | Per token on successful (200) responses |
| Quota pre-check | No | Yes — 402 quota_exceeded |
| Key edition check | Yes (C2/S3 guard) | Not applicable; pool is edition-locked |
| Availability | intl + cn | intl only |
| Data residency | Follows the edition | Overseas — target, unverified (written confirmation pending) |
cn edition and returns 403 mode_B_not_allowed_on_cn. Only the international edition carries resale.Mode B quickstart (international only)
Mode B needs no provider key. You pay for tokens from tokencoup's international Qianfan pool, and the gateway injects the pool key upstream after authenticating your gateway key.
curl -s "https://api.tokencoup.com/intl/b/v1/chat/completions" \
-H "X-Gateway-Key: rf_YOUR_GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "ernie-4.0-8k", "messages": [{"role": "user", "content": "Hello"}]}'const res = await fetch('https://api.tokencoup.com/intl/b/v1/chat/completions', {
method: 'POST',
headers: {
'X-Gateway-Key': process.env.TOKENCOUP_GATEWAY_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'ernie-4.0-8k',
messages: [{ role: 'user', content: 'Hello' }],
}),
});
console.log(res.status, await res.json());503 resale_pool_empty. The local TOKENCOUP_SIM_INTL=1 switch only simulates the pool for development and must never be set in production.Billing & quotas
Only Mode B is billed. Usage is metered on successful (200) responses, written to a usage ledger and charged against the subscription's token quota. The prototype estimates tokens as len(body)/4; production must read the upstream usage field instead.
| Plan | Price | Token quota | RPM (60s window) |
|---|---|---|---|
free | $0 | 100,000 | 60 |
pro | $29 / mo | 5,000,000 | 600 |
scale | $299 / mo | 80,000,000 | 6,000 |
Overage is $0.002 per 1,000 tokens (placeholder). Exhausting the quota returns 402 quota_exceeded. USD→CNY FX (7.2) and the tax rate are placeholders; EU customers require VAT handling before launch.
gateway/plans.py and injects the price, token quota and RPM shown above; a build-time parity check fails if the plan set ever diverges. Both surfaces therefore always match what the gateway actually enforces.Errors
Errors are JSON objects with an error machine code; some also carry detail. Upstream Qianfan errors are passed through with their original status and body.
| HTTP | error | When | What to do |
|---|---|---|---|
| 400 | edition_unknown | Path lacks a valid {intl|cn}/{a|b} prefix | Use a documented path |
| 401 | gateway_auth_failed | Missing, invalid or revoked X-Gateway-Key | Regenerate the gateway key |
| 402 | quota_exceeded | Mode B token quota exhausted | Upgrade plan or wait for the reset |
| 403 | mode_B_not_allowed_on_cn | Mode B requested on the domestic edition | Use /cn/a/… or switch to intl |
| 403 | missing_provider_key | Mode A without a provider key | Add Authorization or X-Provider-Key |
| 403 | key_edition_unknown | Provider key is not registered in tokencoup's edition registry (prefix is not used) | Register the key with an explicit domestic / international edition |
| 403 | key_edition_mismatch | Provider key edition ≠ serving edition (C2/S3) | Match the key to the edition |
| 429 | rate_limited | Plan RPM exceeded | Back off or upgrade |
| 503 | resale_pool_empty | International resale key pool not configured | Provider-side: add an intl Qianfan key |
{
"error": "quota_exceeded",
"detail": "subscription token quota exhausted; upgrade plan or wait for reset"
}Rate limits
Limits are per gateway key, enforced with a 60-second sliding window (Redis when REDIS_URL is configured, in-memory fallback otherwise). The check happens before the upstream call, so a 429 never consumes tokens.
| Plan | RPM | Window |
|---|---|---|
free | 60 | 60 s |
pro | 600 | 60 s |
scale | 6,000 | 60 s |
Recommended: exponential backoff with jitter on 429 and 5xx; never retry 401, 402 or 403 without fixing the request first.
Data residency & privacy (international)
The international edition implements the C2 / S1 design target: requests are routed to the Qianfan International upstream and tagged X-Data-Region: overseas, with log_prompt set to false — prompts are not persisted. Audit records contain only edition, mode, key id and token counts. The X-Data-Region header is an internal routing tag only; it does not control where the upstream processes data and is not evidence of residency. Overseas residency is a target, pending written confirmation from Baidu.
Cloudflare AI Gateway fronts the international path only: 300 s edge cache, 600 req / 60 s rate limiting, 3 retries, collect_logs=false (no request or response bodies stored) and gateway authentication enabled. The domestic edition stays direct.
Machine-readable spec
An OpenAPI 3.1 description of the three path families is published at openapi.json. Import it into Postman, Insomnia or an SDK generator to scaffold a client.
base_url at https://api.tokencoup.com/intl/a/v1 and set X-Gateway-Key as a default header. The gateway does not rewrite payloads, so standard chat-completion helpers work as-is.FAQ
Do I need my own Qianfan account?
Mode A: yes. Mode B: no — tokencoup supplies the international pool key.
Is my prompt stored?
On the international edition, no (log_prompt=false, collect_logs=false). On the domestic edition, prompt logging is enabled for domestic compliance.
Can I use a domestic Qianfan key on the international endpoint?
No. That is the permanently blocked C2/S3 path; the gateway returns 403 key_edition_mismatch.
When am I charged?
Only for Mode B, only on successful (200) responses. Failed requests write a zero-billed ledger row but consume no quota.
Is streaming supported?
Not in v0.2 — the reference server reads and forwards a single JSON response. Set stream: false.