The cheapest way to classify1,000,000 posts
About $21 on Jev, $41 on GPT-5 Nano, $250 on Gemini Flash, $700 on Claude Haiku 4.5 and $1,480 on GPT-5.6 Terra, at 500 input and 40 output tokens per post. A self-hosted fine-tuned classifier runs the job for fifty cents of GPU time and costs two thousand dollars to get there. Every figure is multiplied out below. So is the part nobody mentions: at a million posts the rate limit sets your wall clock long before the price does.
Last updated: September 2026
Free, no signup. A live post scorer you can try now.
The bill
One label plus two yes-no checks per post. Assumptions stated first, so you can argue with a specific number instead of the conclusion.
Assumptions:
- 1,000,000 posts. One request each, no caching, no batch discount unless the row says so.
- 500 input tokens per post. Roughly 150 tokens of post text, which is a 40-word post plus a little JSON wrapper, and roughly 350 tokens of rubric and instructions. The rubric is resent on every request, because none of these paths dedupe it for you.
- 40 output tokens per post for the LLM rows. A small JSON object, no rationale, reasoning pinned to minimal. This is the friendliest possible assumption for the LLMs.
- Totals therefore: 500,000,000 input tokens, 40,000,000 output tokens.
| Model | List rates, per 1M | Input cost | Output cost | Total |
|---|---|---|---|---|
| Jev (jev-1.13.0) | $0.042 in, $0.00 out | $21.00 | $0.00 | $21.00 |
| GPT-5 Nano | $0.05 in, $0.40 out | $25.00 | $16.00 | $41.00 |
| Gemini 2.5 Flash | $0.30 in, $2.50 out | $150.00 | $100.00 | $250.00 |
| Claude Haiku 4.5 | $1.00 in, $5.00 out | $500.00 | $200.00 | $700.00 |
| Claude Haiku 4.5, Batches API | 50% of standard | $250.00 | $100.00 | $350.00 |
| GPT-5.6 Terra | $2.00 in, $12.00 out | $1,000.00 | $480.00 | $1,480.00 |
Jev rates from docs.typesafe.ai/models. GPT-5.6 Terra rates as reported by The Register. Haiku, Gemini Flash and Nano from each vendor's published API pricing, September 2026. Prices move. Re-check every row before you commit a budget to it.
The multiplication, if you want to redo it: 500,000,000 input tokens is 500 units of a million, so the input column is just the input rate times 500. 40,000,000 output tokens is 40 units of a million, so the output column is the output rate times 40. Jev's output column is $0.00 because TypeSafe does not meter output tokens at all. Its own launch post words that as "FREE (too cheap to meter)", and the usage object still reports an output_tokens count that you are simply not charged for.
Notice how much of the LLM bill is the output column: 29 percent of Haiku, 32 percent of Terra, 39 percent of Nano. That is with reasoning pinned to minimal and no rationale field. Let a reasoning model spend 400 tokens thinking before it answers and the output column grows elevenfold while the input column does not move.
The self-hosted option, costed honestly
The comparison people reach for is a fine-tuned encoder. A DistilBERT or DeBERTa-class model around 100M parameters, trained on your own labels, running on one mid-range GPU. It is genuinely the cheapest thing on this page to run and one of the most expensive to get.
| Line item | What it is | Cost |
|---|---|---|
| Inference GPU time | ~1,000 posts/sec on one L4 or A10G class GPU. 1,000,000 posts is under 20 minutes. Round to 0.5 hours at $1.00/hr | $0.50 |
| Fine-tuning run | ~4 GPU hours on the same instance, including two restarts you will need | $4.00 |
| Labeling | 2,000 examples at ~20 seconds each is 11.1 hours of human time, at $25/hr | $278.00 |
| Engineering | Data pipeline, training script, eval harness, serving, monitoring. 3 to 5 days at a $600/day loaded rate | $1,800 to $3,000 |
| First run, all in | What it costs to classify your first million posts this way | $2,083 to $3,283 |
| Second million, marginal | Assuming the taxonomy did not change, which it will | $0.50 |
Throughput and GPU hourly rates are conservative estimates for a 100M-parameter encoder at fp16 with batching. Yours will differ. The labeling and engineering lines are the ones that decide the answer, and they are the ones most cost comparisons leave out.
Here is the payback calculation, which is the only version of this comparison worth having. Self-hosting saves roughly $20.50 per million posts against Jev's $21.00, because its marginal cost is about $0.50. Against a setup cost of roughly $2,100, that is $2,100 divided by $20.50, which is about 102 million posts before you break even.
If you are classifying a million posts once, or a million a month, self-hosting is a way to spend two thousand dollars to save twenty. If you are classifying a hundred million a month and the taxonomy is stable, it is obviously correct, and at that scale you should also be talking to TypeSafe about the request limit rather than reading a marketing page.
The other thing the table shows: labels are the real cost of a classifier, not GPUs. That cost recurs every time the taxonomy changes. The models on this page need zero labels to start, which is the actual argument for them.
The part nobody costs: your wall clock
At a million posts the binding constraint is the request limit, not the price, and it is not close.
TypeSafe publishes two ceilings: 250,000 tokens per second and 1,200 requests per minute. At 500 tokens per request those two numbers are badly mismatched, and one of them does all the work.
- Request ceiling: 1,000,000 requests / 1,200 per min = 833 min = 13.9 hours
- Token ceiling: 500,000,000 tokens / 250,000 per sec = 2,000 sec = 33 min
- Utilisation of the token ceiling: 500 x 1,200 / 60 = 10,000 tokens/sec, which is 4%
The token ceiling is barely touched at this request shape. The exact crossover is easy to derive: 250,000 tokens/sec is 15,000,000 tokens/min, and 15,000,000 divided by 1,200 is 12,500. Below 12,500 tokens per request the request cap binds. Above it, the token cap does. At exactly 12,500 they bind together and you are running the pipe flat out.
| Batch shape | Requests | Tokens/request | Request-limit time | Token-limit time | Whichever binds |
|---|---|---|---|---|---|
| 1 post per request | 1,000,000 | 500 | 25x | 1x | 25x |
| 5 posts per request | 200,000 | 2,500 | 5x | 1x | 5x |
| 25 posts per request | 40,000 | 12,500 | 1x | 1x | 1x |
| 50 posts per request | 20,000 | 25,000 | 0.5x | 1x | 1x |
Times are relative to the token ceiling, which is the arithmetic above on TypeSafe's published ceilings and assumes you keep enough requests in flight to saturate them. TypeSafe notes that rate limits adjust dynamically and may change without notice, so treat these as the shape of the problem rather than a schedule.
Batching buys requests, not tokens, and it costs accuracy
Each post you add to a batch needs its own question with its own instructions, so the input total barely changes and neither does your bill. What changes is the request count. The cost is that every question in the request now sees every post, and TypeSafe's own model-jaggedness page says irrelevant context degrades accuracy and tells you to "retrieve and filter in code first, and send only the fields the question needs." Batch, measure against an unbatched baseline, and stop when the labels move.The code
Unbatched first, with a semaphore because the limit you will hit is requests per minute and an unthrottled gather over a million items will simply 429.
import asyncio
from typesafe_sdk import AsyncTypeSafeClient, Choice, Noul
RUBRIC = {
"product": "A launch, a feature, a release, a changelog",
"opinion": "A take, an argument, a prediction",
"personal": "Life, travel, family, health",
"promo": "Asks the reader to buy, click, sign up or subscribe",
}
async def classify(client, post):
response = await client.system_one(
state={"post": post["text"], "platform": "X"},
model="jev-1.13.0", # pin it. jev-latest moves under you.
questions={
"topic": Choice(instructions="What is this post mainly about", criteria=RUBRIC),
"is_spam": Noul(instructions="This post is spam or an unsolicited advertisement"),
"is_rage_bait": Noul(
instructions="This post is written to provoke an angry reply rather than to inform",
),
},
)
topic = response.answers["topic"]
return {
"id": post["id"],
"topic": topic.choice,
"topic_confidence": topic.confidence,
"spam": response.answers["is_spam"].noul, # a float, not a bool
"rage_bait": response.answers["is_rage_bait"].noul,
"input_tokens": response.usage.input_tokens, # the only billed number
}
async def run(posts, concurrency=20):
sem = asyncio.Semaphore(concurrency)
async with AsyncTypeSafeClient() as client:
async def guarded(post):
async with sem:
return await classify(client, post)
return await asyncio.gather(*(guarded(p) for p in posts))And the batched shape, with the two checks that decide whether it is safe.
from typesafe_sdk import Choice, Noul
# Batching posts into one request trades accuracy risk for request headroom.
# It does NOT save tokens: each post still needs its own question with its own
# instructions, so the input total is roughly unchanged. What drops is the
# request count, which is the ceiling that actually binds.
def batched_questions(posts):
questions = {}
state = {}
for i, post in enumerate(posts):
state[f"post_{i}"] = post["text"]
questions[f"topic_{i}"] = Choice(
instructions=f"What is post_{i} mainly about",
criteria=RUBRIC,
)
questions[f"spam_{i}"] = Noul(
instructions=f"post_{i} is spam or an unsolicited advertisement",
)
return state, questions
# Budget check before you send it:
# state + longest question must stay under 32k tokens
# the whole request must stay under 64k tokens
#
# Accuracy check you must actually run:
# score 500 posts batched and unbatched, compare label by label.
# TypeSafe's own docs warn that irrelevant context degrades answers,
# and in a batch every question sees every post.The field that tells you whether a Jev article was written by someone who ran it
A Noul answer returnsnoul, a float from 0 to 1, and nothing else. There is no confidence field on a Noul. Only Choice and Score carry one. Threshold the float yourself, and pick the threshold from your own labeled sample rather than from any number on this page.Two operational notes. Pin the model to jev-1.13.0 rather than jev-latest, so a version bump does not silently shift the distribution your thresholds were tuned against. And handle 429 with exponential backoff and 529 as a transient overload, which is what TypeSafe's error table tells you to do. Full limits and error codes.
Pick by what the job actually is
Jev fits when
- The output is a label, a level or a yes-no, and nothing has to be written.
- You want more than one judgment per post, because extra questions are free on the output side.
- You want a probability per option so code can route the uncertain ones to a human.
- The corpus is text, in English, and each item fits comfortably inside the 32k budget.
- You have no labels and no appetite for a labeling project.
- You can live with a single closed-weight vendor in waitlist-gated early access.
Use something else when
- You need a summary, a rewrite or an extracted free-form string. Jev writes nothing.
- The input includes images, audio or video. Jev is text only.
- The task is counting, date arithmetic or a cross-field invariant. Do those in code.
- You are running over 100 million items a month with a stable taxonomy. Fine-tune.
- The data cannot leave your network. There is no self-host and no open weights.
- A 24-hour turnaround is fine and you need a capability only a frontier model has.
Then the results have to go somewhere
Classification is the middle of a pipeline, not the end of one. Jev decides, an LLM writes whatever needs writing, and publishing the result is one request that fans out to three networks.
curl -X POST https://opentweet.io/api/v1/posts \
-H "Authorization: Bearer ot_your_key" \
-H "Content-Type: application/json" \
-d '{
"text": "We classified a million posts this week. 3.1% were rage bait.",
"publish_now": true,
"platforms": ["x", "bluesky", "linkedin"]
}'The same surface is exposed as a hosted MCP server, so an agent takes the same action without a second integration. Get an API key, read the docs, or see how to build a post scorer on Jev.
Frequently asked questions
What is the cheapest way to classify 1 million posts?
On list prices at 500 input tokens and 40 output tokens per post, Jev is about $21, GPT-5 Nano about $41, Gemini Flash about $250, Claude Haiku 4.5 about $700 and GPT-5.6 Terra about $1,480. A self-hosted fine-tuned encoder is about $0.50 of GPU time for the run itself, but roughly $2,100 to $3,300 once you count labeling and engineering the first time, so it only wins if you run the job repeatedly. Jev is the cheapest number you can reach without owning anything, because it bills $0.042 per million input tokens and does not meter output at all.
Why does Jev cost so much less than an LLM for this?
Two reasons, and only one of them is the input price. Jev is $0.042 per million input tokens against $1.00 to $2.00 for the small and mid frontier models, which is a 20x to 50x gap. The bigger structural difference is output: Jev does not decode text at all, so there is no output bill, while a classification on an LLM pays for every token of the JSON it writes and for every reasoning token it spends first. At 40 output tokens per post, output is 29 percent of the Haiku bill and 32 percent of the Terra bill. Let the model reason before it answers and it becomes the majority.
How long does classifying a million posts actually take on Jev?
Whether it takes half an hour or most of a day is decided by the request limit rather than the token limit, and you can derive it from the two ceilings TypeSafe publishes: 250,000 tokens per second and 1,200 requests per minute. At 500 tokens per request those two ceilings are wildly mismatched: 1,000,000 requests takes 833 minutes at 1,200 per minute, while 500 million tokens takes only 33 minutes at 250,000 per second. You are using 4 percent of your token throughput. The crossover sits at exactly 12,500 tokens per request, because 250,000 tokens per second is 15,000,000 per minute and 15,000,000 divided by 1,200 is 12,500. Below that the request cap binds, above it the token cap does.
Can I batch several posts into one Jev request?
You can, and it is the only lever that moves the request ceiling, but it does not save you money and it carries a real accuracy risk. Jev takes a state that may be a JSON object or array, and each post you add needs its own question with its own instructions, so the token total barely changes. What changes is the request count. The risk is that every question in the request now sees every post, and TypeSafe's own model-jaggedness page warns that irrelevant context degrades accuracy and advises you to "retrieve and filter in code first, and send only the fields the question needs". Batch modestly, measure batched output against an unbatched baseline on a sample, and stop increasing the batch when accuracy moves.
Is a fine-tuned classifier cheaper than Jev at this volume?
On marginal cost, yes, by roughly 40x. On total cost for one run, no, by roughly 100x. A 100M-parameter encoder at around 1,000 posts per second finishes a million posts in under 20 minutes of GPU time, which is about $0.50 on an L4 or A10G class instance. The money is in the labels and the build: roughly 2,000 labeled examples at 20 seconds each is 11 hours of human time, and the pipeline, eval and serving are several engineer-days. At about $20.50 saved per million posts the setup pays back after roughly 100 million posts. Below that, you are paying thousands to save twenty dollars.
Do batch APIs change the answer?
They narrow the gap and cost you a day. Anthropic bills the Message Batches API at 50 percent of standard rates, so the Haiku run drops from about $700 to about $350 against a 24-hour processing window. Other vendors publish similar discounts. TypeSafe documents no batch endpoint and no batch discount for Jev, so Jev's $21 is already its synchronous price. Trading a day of latency to reach $350 when $21 is available synchronously is a trade worth thinking about only if you need something Jev cannot do, such as generated text in the same call.
What does Jev not do well at this volume?
Anything that is arithmetic rather than judgment. TypeSafe documents this clearly: counting is unreliable and should be done in code, dates and times are treated as text rather than ordered values so windows and durations are unreliable, instructions are read literally so negations and implied conditions land at face value, and no structural invariants hold between separate questions. It also takes text only, with no image, audio or video input, and it produces no text of any kind. Extraction of a value that is not in your declared answer space is not something it can do.
What do I do with the results?
Whatever the classification was for. If the output is a set of posts worth publishing, the publishing step is a single POST to https://opentweet.io/api/v1/posts with a text field and a platforms array, which fans out to X, Bluesky and LinkedIn in one call. The same surface is exposed as a hosted MCP server, so an agent can take the same action without a second integration.
Keep reading
The rest of the cost work, the limits that bound it, and the publishing half.
Jev vs GPT-5 Nano
The same arithmetic per 1,000 items, and why the gap is 2x on a label and 10x with reasoning.
Jev limits and rate limits
64k per request, 32k for state plus the longest question, 250k tokens/sec, 1,200 requests/min.
Jev cost calculator
Your token counts, your volume, your number, instead of a worked example built on numbers that are not yours.
Classifying text with Jev
Choice, Score and Noul on a real corpus, with the request and response JSON.
Will it go viral, a live Jev scorer
Free, no signup. Seven Jev questions on a draft, scored live.
The OpenTweet API
One POST to /api/v1/posts publishes to X, Bluesky and LinkedIn.
Twenty-one dollars to decide. One call to ship.
The free post scorer at /tools/will-it-go-viral needs no signup. Publishing to X, Bluesky and LinkedIn starts at $11.99 a month, with the REST API and the MCP server on every plan.
- 7-day free trial
- No X developer account needed
- Cancel anytime