AI
Bahasa Indonesia LLM Token Cost: 1.23x, Not 2-3x, Measured
September 202612 min read

Not on a current tokenizer. Measured across 20 matched English and Indonesian sentence pairs with gpt-tokenizer, Indonesian came to 369 tokens against 300 for English on o200k_base, a ratio of 1.23x. The 2-3x figure circulating online is closer to what older encodings produced, and even then it overstates the case.
o200k_base carries a much larger vocabulary, and the extra slots went disproportionately to non-English sequences because English was already well covered. On the same corpus the penalty fell from 52 percent to 23 percent, while the English side stayed at exactly 300 tokens. Words like pengiriman and keuangan went from three tokens to two.
Use the vendor's count-tokens endpoint. Anthropic exposes POST /v1/messages/count_tokens, which is documented as free to use and rate-limited separately from generation, and Google exposes models.countTokens. Both run the tokenizer of the model id you pass, so count against the exact model you plan to ship rather than reusing an older number.
Usually not. You add an entire extra API call, including its output tokens, to save roughly 23 percent of the input tokens on one call, and you pay two round trips of latency in a live chat. You also lose Indonesian register such as the difference between Anda and kamu. It only pays off in batch document pipelines where nobody reads the Indonesian.
No, and the average hides that. Three of the 20 Indonesian sentences I measured were cheaper than their English twin, with the best at 0.72x, because Indonesian drops articles and does not inflect for tense. The worst pair ran 1.64x because it was built from affixed verbs like diperpanjang and membatalkannya.

Key Takeaway
Indonesian text costs about 23 percent more tokens than the same meaning in English on OpenAI's o200k_base encoding, measured across 20 matched sentence pairs, not the two to three times widely repeated. The older cl100k_base charged 52 percent. Three of those 20 Indonesian sentences were actually cheaper than their English twin.
I was costing an Indonesian-language support assistant and needed exactly one number: how much more does Bahasa Indonesia cost per message than English. Every source I found repeated the same 2x to 3x multiplier, none of them showed a measurement, and most of them were quietly citing each other. At 2.5x the project was marginal. At 1.2x it was fine. Nobody could tell me which one it was.
So on 18 September 2026 I measured it. Twenty English and Indonesian sentence pairs, written by hand in the register a real product actually sends - support replies, ERP notices, product copy, privacy text - run through the gpt-tokenizer package against both o200k_base and cl100k_base. This post is that measurement: what the number is, why it moved, why averaging it is misleading, and which mitigations are worth the trouble.
Indonesian cost 23 percent more tokens than English for the same 20 sentences on o200k_base, the encoding behind the GPT-4o and GPT-5 families. The corpus came to 300 English tokens against 369 Indonesian ones. On cl100k_base, the older encoding used by GPT-4 and GPT-3.5 Turbo, the same text ran 300 against 456 - a 52 percent penalty. The English side did not move by a single token between the two encodings.
| Encoding | Model family | EN chars per token | ID chars per token | Corpus tokens EN / ID | ID to EN ratio |
|---|---|---|---|---|---|
| o200k_base | GPT-4o and GPT-5 families | 5.40 | 4.38 | 300 / 369 | 1.23x |
| cl100k_base | GPT-4 and GPT-3.5 Turbo | 5.40 | 3.54 | 300 / 456 | 1.52x |
Characters per token is the unit that survives this comparison, because a raw token count rewards whichever language says the thing in fewer characters. In this corpus neither does: the English half is 1,619 characters and the Indonesian half is 1,615, four characters apart. The two halves are the same length on the page and a different length in the billing system, which is the whole point. English packs 5.40 characters into a token here and Indonesian 4.38. The measurement needs no API key and finishes in under a second.
// npm i [email protected] - OpenAI's BPE encodings, offline, no API key.
import { encode as o200k } from "gpt-tokenizer/encoding/o200k_base";
import { encode as cl100k } from "gpt-tokenizer/encoding/cl100k_base";
// Both sides hand-written by the same person in the same register. Feeding
// machine translation in here measures the translator, not the language.
const pairs = [
["The refund has been processed and will appear in your account within three working days.",
"Pengembalian dana sudah diproses dan akan masuk ke rekening Anda dalam tiga hari kerja."],
["Your subscription will renew automatically unless you cancel it before the billing date.",
"Langganan Anda akan diperpanjang secara otomatis kecuali Anda membatalkannya sebelum tanggal penagihan."],
// ...18 more: support replies, ERP notices, product copy, privacy text
];
for (const [name, enc] of [["o200k_base", o200k], ["cl100k_base", cl100k]]) {
let en = 0, id = 0, enChars = 0, idChars = 0;
for (const [a, b] of pairs) {
en += enc(a).length; id += enc(b).length;
enChars += a.length; idChars += b.length;
}
// chars/token, not raw token count. Raw counts flatter whichever language
// happens to need fewer characters to say the same thing.
console.log(name, (id / en).toFixed(2) + "x",
"EN", (enChars / en).toFixed(2), "ID", (idChars / id).toFixed(2));
}
// o200k_base 1.23x EN 5.40 ID 4.38
// cl100k_base 1.52x EN 5.40 ID 3.54gpt-tokenizer is a pure TypeScript implementation of OpenAI's BPE encodings with no native dependency and no network call, so you can count a million strings in CI without a key or a bill. Version 4.0.0 exposes each encoding behind its own import path, so you load the one vocabulary you need rather than all of them.
The penalty is affixation. Indonesian builds a single written word out of a prefix, a root, a suffix and often a clitic, and these encodings learned their vocabulary on text that is overwhelmingly English, so a long Indonesian word has no token of its own and gets cut into pieces. Decoding each token id back to its own string shows exactly where the cuts land.
// Each token id decoded back to its own string, so you can see the seams.
// o200k_base, the encoding behind the GPT-4o and GPT-5 families.
membatalkannya 5 tokens m | emb | at | alk | annya
ketidaknyamanannya 5 tokens ket | idak | ny | aman | annya
dijadwalkan 5 tokens d | ij | ad | walk | an
memverifikasi 3 tokens mem | ver | ifikasi
penghapusan 3 tokens peng | hap | usan
// One Indonesian orthographic word is often prefix + root + suffix + clitic.
// "membatalkannya" is meN- plus batal plus -kan plus -nya: cancel it. English
// spends three cheap words on that; Indonesian spends one expensive one.
// The other direction is why the tax is 1.23x and not 2x. The loanwords a
// real product leans on are already whole tokens:
invoice 1 token invoice
pelanggan 2 tokens pel | angganThe second half of that output is why the tax is 1.23x and not 2x. Formal Indonesian is affix-heavy, but the working vocabulary of a real Indonesian product is full of English loanwords that already have a token to themselves - invoice was one token in both encodings in my corpus - and Indonesian spends nothing on the articles English pays for on every noun. The affixed verbs cost; a lot of the nouns do not. Which of the two your copy is made of decides the ratio you end up with.
1.52x became 1.23x with no change to the text at all. o200k_base carries a substantially larger vocabulary than cl100k_base, and the extra slots went disproportionately to sequences that are not English, because English was already well covered. Three words from the corpus show the mechanism directly:
The English half of the corpus came to exactly 300 tokens under both encodings, so every token the newer encoding saved here, it saved on the Indonesian side. That is the finding a budget actually cares about. Anyone pricing an Indonesian workload off a 2023 article is budgeting for a penalty that has already been paid down by roughly half, on a model generation they are almost certainly not shipping any more.
Do not assume the direction is always down. Anthropic's token-counting documentation states that Claude 4.7 and later models use a newer tokenizer, that the same input text produces approximately 30 percent more tokens than on earlier models, and that you should recount rather than reuse counts measured against an earlier model. A tokenizer is a pricing variable, and it can move against you between two models from the same vendor.
The average hides a range of 0.72x to 1.64x. The median pair ran 1.29x, above the corpus ratio, and three of the 20 Indonesian sentences cost fewer tokens than their English twin on o200k_base. These are the ends of that distribution.
| Sentence pair | EN tokens | ID tokens | Ratio |
|---|---|---|---|
| Payment made, invoice not yet received | 18 | 13 | 0.72x |
| Monthly stock count on the last working day | 17 | 16 | 0.94x |
| Free shipping above a rupiah threshold | 13 | 20 | 1.54x |
| Subscription renews unless cancelled in time | 14 | 23 | 1.64x |
The cheap end is Indonesian doing what it is good at: no articles, no tense inflection, and two English loanwords sitting in one short sentence. The expensive end is a policy sentence assembled out of affixed verbs - diperpanjang, membatalkannya, penagihan - which is precisely the register that terms of service, ERP notices and billing emails are written in. If your product's Indonesian is mostly formal notices, budget nearer the top of that range than the middle of it.
Run the script over your own message catalogue rather than mine. Twenty sentences is enough to disprove 2.5x and nowhere near enough to set a budget. Point it at the strings you actually send - the system prompt, the few-shot block, a month of real user turns - and the ratio you get belongs to your product instead of somebody else's corpus.
Everything above holds for OpenAI's encodings, because OpenAI is the vendor that publishes them: tiktoken ships the BPE vocabularies as open source, which is the only reason an offline count is possible at all. Anthropic and Google do not offer an equivalent, and the honest method there is the count-tokens endpoint - POST to the messages count_tokens path for Claude, models.countTokens for Gemini. Anthropic documents its endpoint as free to use, rate-limited separately from generation, and returning an estimate that can differ from the billed figure by a small amount.
# Anthropic publishes no offline tokenizer, so you count over the wire.
# The endpoint is free and rate-limited separately from generation.
curl https://api.anthropic.com/v1/messages/count_tokens \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-5",
"messages": [{
"role": "user",
"content": "Langganan Anda akan diperpanjang secara otomatis."
}]
}'
# -> {"input_tokens": 14}
# Google is the same shape: models.countTokens runs the model's own tokenizer.
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:countTokens?key=$GEMINI_API_KEY" \
-H "Content-Type: application/json" -X POST \
-d '{"contents":[{"parts":[{"text":"Langganan Anda akan diperpanjang otomatis."}]}]}'
# The model id is not decoration. Anthropic counts under the tokenizer of the
# model you pass, and its docs say Claude 4.7 and later produce about 30 percent
# more tokens for the same text than earlier models. A count is per model, not
# per vendor - and never per blog post.That distinction is bigger than it sounds. An offline tokenizer lets you count a million strings inside a test. A network endpoint turns the same language-ratio experiment into a rate-limited batch job tied to one specific model id, which you have to budget for and then repeat every time you change models - which is exactly what Anthropic's documentation tells you to do rather than reusing an older count.
Published list prices per million tokens, read from each vendor's own pricing page on 18 September 2026. They move, so check them rather than quoting this table back at anyone.
| Provider | Model | Input per 1M tokens | Output per 1M tokens |
|---|---|---|---|
| OpenAI | gpt-5 | $1.25 | $10.00 |
| Anthropic | Claude Sonnet 5 | $2.00 | $10.00 |
| Gemini 2.5 Flash | $0.30 | $2.50 |
Take a workload whose English version would bill one million input tokens. The Indonesian version bills 1,230,000 - an extra 230,000 tokens. That is 29 cents more on gpt-5, 46 cents more on Claude Sonnet 5, and 7 cents more on Gemini 2.5 Flash. On cl100k_base the same workload would have carried 520,000 extra tokens instead of 230,000, which is where the frightening numbers in older articles came from.
Output is where the penalty actually lands, because output costs at least five times input on every row of that table - the same 230,000 extra tokens are 2.30 dollars as gpt-5 output rather than 29 cents as input. And the comparison nobody makes is the one that puts it in proportion: input on Claude Sonnet 5 is 6.7 times input on Gemini 2.5 Flash. The language is 1.23x. Model choice and caching are the levers; the language is an adjustment on top of them.
Three of these are worth doing. The fourth is the one people reach for first, and it is usually a false economy.
Which leaves the one that sounds cleverest: translate the user's Indonesian into English, prompt in English, translate the answer back. You are adding an entire extra API call to save 23 percent of the input tokens on one call, and the extra call produces output tokens, which are the expensive kind. You also pay two round trips of latency inside a chat UI, and you lose the register. Indonesian marks formality in ways English simply does not - Anda against kamu, the distance between Mohon maaf and Maaf ya - and a round trip through English flattens all of it. The flattening is visible to the customer in a way a token count never is.
Translate-first earns its place in exactly one shape: a batch pipeline over documents, where nobody reads the Indonesian output, latency is free, and the translation itself runs on a cheap model. In a live support chat it costs money, time and tone at the same time.
Four limits, all of them mine.
None of that makes the exercise useless. It makes it a method rather than a number: the script is twenty lines, your corpus is already sitting in your repository, and the ratio it gives you is the only one that belongs in your spreadsheet.
A language multiplier that arrives without a measurement attached is a rumour, and 2x to 3x is the loudest rumour in Indonesian AI costing. The real figure on a current OpenAI encoding was 1.23x for my corpus, it was 1.52x two model generations ago, and three sentences in twenty went the other way entirely. Measure your own strings against the model id you will actually ship, re-measure when you change it, and then go and spend the afternoon on prompt caching - where a cache read is priced at a tenth of base input, and the saving dwarfs anything the language ever cost you.
Sources and further reading