How costs are calculated
USD cost × exchange rate × (1 + margin)
Pricing happens in two stages. First we derive a per-model unit price in won per token; then we multiply it by the tokens a request actually used. Both calculations exist exactly once in the server code, so the dashboard, the API responses, and the credit ledger always agree.
Stage 1 — the unit price
Upstream providers quote their prices in USD. Here is how that becomes a won price:
m = 1 + margin_pct / 100
krw_prompt = override_krw_prompt ?? usd_prompt × fx × m
krw_completion = override_krw_completion ?? usd_completion × fx × m
krw_cache_read = override_krw_cache_read ?? (usd_cache_read missing → krw_prompt × 0.1
usd_cache_read present → usd_cache_read × fx × m)| Symbol | Meaning | Current value |
|---|---|---|
fx | Exchange rate, KRW per USD | 1,350 |
margin_pct | Margin percentage | 30 (global default) |
override_krw_* | A sale price set directly by an administrator | May or may not exist per model |
Rules are looked up per model first, then globally. A model-specific margin or override wins over the global default.
Cache-read pricing
If the upstream does not publish a cache-read price for a model, cache-hit tokens are charged at 10% of the prompt price. If it does publish one, the same exchange rate and margin apply to it.
Cache write tokens are recorded but not billed, because upstreams do not report them consistently.
Rounding
- Unit prices are rounded once, to 12 decimal places.
- The only other rounding is the request cost, at 6 decimal places.
Nothing is rounded in between, so error does not accumulate no matter how many tokens a request uses.
Example: one external model
A model whose upstream cost is $0.50 per 1M prompt tokens and $1.50 per 1M completion tokens, sold at the default 30% margin:
krw_prompt = 0.0000005 × 1350 × 1.3 = 0.000877500000 KRW/token → ₩877.5 / 1M tokens
krw_completion = 0.0000015 × 1350 × 1.3 = 0.002632500000 KRW/token → ₩2,632.5 / 1M tokens
krw_cache_read = 0.000877500000 × 0.1 = 0.000087750000 KRW/token → ₩87.75 / 1M tokensStage 2 — the request cost
billable_prompt = prompt_tokens - cached_tokens
cost_raw = billable_prompt × krw_prompt
+ cached_tokens × krw_cache_read
+ completion_tokens × krw_completion
cost_krw = ROUND(cost_raw, 6)Things worth knowing:
cached_tokensarrives insideprompt_tokens. We subtract it and re-count it at the cache price; it is not double-billed.- Reasoning tokens (
reasoning_tokens) are already part ofcompletion_tokens. They are not added again. - Rounding happens once, at the end.
Walkthrough — external model
With the unit prices above, a request with 1,200 prompt tokens (200 of them a cache hit) and 800 completion tokens:
billable_prompt = 1200 - 200 = 1000
1000 × 0.0008775 = 0.877500
200 × 0.00008775 = 0.017550
800 × 0.0026325 = 2.106000
─────────────
cost_krw = 3.001050 KRWThat number comes straight back in the response headers:
X-MyIP-Cost-KRW: 3.001050
X-MyIP-Currency: KRWWalkthrough — local GPU model
google/gemma-4-26b-a4b and lgai/exaone-4.0-32b run on our own GPUs and carry a directly configured sale price.
| Item | Unit price | Per 1M tokens |
|---|---|---|
| Prompt | 0.000030 KRW/token | ₩30 / 1M tokens |
| Completion | 0.000150 KRW/token | ₩150 / 1M tokens |
For 2,000 prompt tokens (no cache) and 1,000 completion tokens:
2000 × 0.000030 = 0.060000
1000 × 0.000150 = 0.150000
─────────────
cost_krw = 0.210000 KRWThe 1,000 KRW signup bonus alone covers more than 4,700 requests of that size.
Where to read the unit prices
The pricing object in GET /api/v1/models is the sale price. Field names and the string encoding follow the OpenAI-compatible catalogue shape; only the unit differs — these are won per token.
curl -s "https://openrouter.myip.co.kr/api/v1/models" \
| jq '.data[] | select(.id=="lgai/exaone-4.0-32b") | {id, pricing}'{
"id": "lgai/exaone-4.0-32b",
"pricing": {
"prompt": "0.000030000000",
"completion": "0.000150000000",
"input_cache_read": "0.000003000000",
"input_cache_write": null,
"currency": "KRW"
}
}Reconciling a single request
Once a request is settled, GET /api/v1/generation returns what it actually cost.
curl -s "https://openrouter.myip.co.kr/api/v1/generation?id=$GEN_ID" \
-H "Authorization: Bearer $MYIP_API_KEY" | jq '{total_cost, tokens_prompt, tokens_completion}'total_cost is exactly the amount written to the credit ledger. The gen-… id comes back in the X-MyIP-Generation-Id response header.
For streaming responses the cost rides on the final usage chunk:
{
"usage": {
"prompt_tokens": 1200,
"completion_tokens": 800,
"total_tokens": 2000,
"cost": 3.00105,
"cost_details": { "upstream_inference_cost": 2.295 }
}
}A streamed response cannot carry the cost in a header — headers are sent before the first byte — so this chunk is the only channel. You do not need to set stream_options.include_usage yourself; we always request usage from the upstream.
When nothing is charged
| Situation | Charge |
|---|---|
| Request failed with an upstream error | 0 KRW, no ledger entry |
| Both prompt and completion tokens are zero | 0 KRW, no ledger entry |
| Client cancelled the stream mid-flight | Billed for the tokens generated so far |
| First candidate in a fallback chain failed, second succeeded | Billed once, at the unit price of the candidate that actually answered |
When prices change
The exchange rate, the margin, and per-model sale prices live in history tables with validity ranges. Changing a value inserts a new row and closes the old one. Past rows are never edited.
On top of that, every usage record freezes the prompt unit price, completion unit price, exchange rate, and margin that were applied to it. Even after today's prices change, yesterday's charge can be reproduced exactly.
Related
- Credits and payment — top-ups, receipts, refunds
- Limits and 402 — what happens when the balance runs out
Last updated Sep 5, 2026