# classifier.dev

zero-shot text classification over plain HTTP — no API key, no account

Start without an account. Create a workspace when you want shared usage, billing and API keys. See pricing →

## Give your agent this prompt

Paste it into any coding agent. It installs the skill and teaches the agent to classify text through this API. No key, no setup.

Set up the classifier.dev skill: run `npx skills add https://classifier.dev`, then read https://classifier.dev/skill.md and follow it all the way through.

or open it in[Claude Code][Codex][Cursor][Grok]

## Better than calling its own model directly

The model behind this service is Jev. The smart tier re-asks only what Jev was unsure about, and comes out +2.5 points on AG News (+18.4 on the unsure items), +1.0 points on emotion (+4.9 on the unsure items). On 400 items a gap under about 5 points is noise; AG News clears it. Same public test sets, measured live over this API on 2026-09-18. No key, no cost.

AG Newsemotion
allunsure (49)allunsure (122)
jev alone = classifier.dev fast87.5%65.3%61.8%36.9%
classifier.dev smart90.0%83.7%62.7%41.8%
smart over jev alone, points+2.5+18.4+1.0+4.9

[full benchmark][npm run vs-jev]

## Try it

curl https://classifier.dev/spam,not+spam/Win+a+free+iPhone
spam

## Install the CLI

npm i -g classifier-dev

Then sort a file, one label ⇥ confidence ⇥ text line per input, in input order — a thousand lines a request, and rows appear as they land:

classify bug,feature,praise < feedback.txt
classify relevant,"not relevant" --review 0.7 < snippets.txt   # only the unsure ones

Zero-shot text classification over plain HTTP. You send text and a list of labels, you get back the label that fits and how sure the model is. There is no API key or account required for free use, so the example below works the moment you paste it.

Agents: the OpenAPI 3.1 description is at https://classifier.dev/openapi.json and a short index at https://classifier.dev/llms.txt

## Typesafe sdk compatibility

classifier.dev implements TypeSafe's System One wire contract at the same paths as TypeSafe. Point the official JavaScript or Python SDK at this origin; Choice, Noul and Score questions, model listing, usage, request IDs, validation errors and retry headers keep their native shapes.

npm install @typesafe-ai/sdk

import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient({
  apiKey: process.env.CLASSIFIER_API_KEY ?? "unused",
  baseURL: "https://classifier.dev",
});
const result = await client.systemOne({
  state: "I was charged twice. Please fix this today.",
  questions: {
    category: choice("Which team should handle this?", {
      billing: null,
      technical: null,
    }),
  },
});
console.log(result.answers.category.choice);
# uv add typesafe-sdk
from typesafe_sdk import Choice, TypeSafeClient
with TypeSafeClient(api_key="unused", base_url="https://classifier.dev") as client:
    result = client.system_one(
        state="I was charged twice. Please fix this today.",
        questions={"category": Choice(
            instructions="Which team?",
            criteria={"billing": None, "technical": None},
        )},
    )

The apiKey selects how classifier.dev accounts for POST /v1/systemone:

"unused" or any non-workspace value
  Free anonymous use. The value is ignored and never forwarded. Fast-tier
  quota is counted by questions: 3,000/minute and 20,000/day per IP.
classifier_agent_...
  A workspace key from https://classifier.dev/app/keys. Requests use the
  workspace's shared quota and credit balance. Free workspaces keep the
  same ceilings; Pro workspaces get 10x limits. Charges use TypeSafe's
  returned token usage and appear in workspace usage history.

Do not put a real TypeSafe API key here: classifier.dev never forwards caller credentials. GET /v1/models is public and does not spend quota or credits. The corresponding HTTP resources are POST /v1/systemone and GET /v1/models.

## Laya and kev

Calls with neither model nor processing use Jev. Two other models are available, both hosted by Beam on shared inference endpoints:

model: "laya"   ModernBERT-large with a trained decision head. Answers
                every question in one batched forward pass. State plus one
                question must fit 512 tokens.
model: "kev"    Qwen2.5-0.5B with released LoRA weights and a pointer
                head. Encodes state once and scores question branches
                together in one 8,192-token packed sequence.

Omit processing to automatically choose fast for one decision (up to four multi-label questions), or bulk for larger work. Supplying processing without model implies Laya. Explicit lanes are honored. With explicit model: "jev", processing is accepted but has no effect. Each result is labelled jev/laya or jev/kev; neither model reports a checkpoint.

{"model":"laya","processing":"fast","input":"Please refund this charge",
 "labels":["billing","technical"]}

Fast: one decision per call, 60 questions/minute and 2,000/day per caller. Bulk: up to 1,000 questions per call, 1,000/minute and 20,000/day. Large calls are chunked internally and results retain input order. A single-label decision is one question; multi-label uses one question per label. Fast accepts at most four questions. Multiple dimensions normally need bulk. Trial limits also apply to paid and operator keys; existing Smart quotas still apply. Shared capacity can return 429 even with quota remaining. Quotas count attempted questions, including failed inference.

Both models accept short text: at most 2,000 characters, 2–16 labels of at most 100 characters each, and instructions up to 400 characters. The text, question and labels must also fit the model's context — 512 tokens for Laya, 8,192 for Kev — and oversized content is rejected, not silently shortened. Upstream caps a request at 32 questions, so a large batch is split into several requests and reassembled in input order.

Jev's published accuracy and calibration measurements do not describe either model. We have not fitted temperatures on classifier.dev traffic: treat scores and Smart's confidence-triggered reviews as experimental, not a quality guarantee.

Neither model has a warm pool to start, so there is no cold-start 503. On 429, respect Retry-After and use bounded retries with backoff. A refused request is not retried upstream: the refusal is returned as it happened. Shared endpoints are not a replica in every region or a latency promise. Accepted work is held only in memory; there is no durable batch-job service. Overload never silently switches the model or processing lane.

Laya and Kev inference has no retail charge during this trial. Optional tier: "smart" reviews remain separately priced as before and can be slower.

From this repository's CLI:
  node cli/classify.js billing,technical --model laya "Please refund this"
  node cli/classify.js billing,technical --model kev --processing bulk < tickets.txt

## Long documents

An input over 32,000 characters is answered by chunklaya, our own long-document service, when it is configured; otherwise it is refused with input_too_long as before. chunklaya is Laya behind a harness: the document is split into passages and indexed once, and every question in the request is answered off that index, so a request with dimensions asks all of them in one pass. Up to 4,000,000 characters per input and 20 inputs per request. The result is labelled chunklaya/multilingual. Nothing at or under 32,000 characters changes, and an explicit model: "jev" keeps the 32,000 ceiling; model: "chunklaya" selects it for shorter text too.

{"input": "<a 300,000-character contract>",
 "dimensions": {"kind": ["lease", "employment", "supply"],
                "renews": ["automatically", "on notice", "never"]}}

What it will not do. tier: "smart" is refused with bad_tier: the reviewing model cannot read the document. A document with more passages than the service scores in one request (256 paragraphs), or a request with more questions than fit, is refused with chunklaya_input rather than answered from part of the text. A busy service answers 429 chunklaya_busy with Retry-After; an unreachable one 503 chunklaya_unavailable. There is no fallback to another model.

Accuracy on long documents has not been measured against Jev on classifier.dev traffic; the harness's own results are in its repository, github.com/myxamediyar/chunklaya. No retail charge during this trial.

## Against the model it runs on

The model behind this service is Jev; the smart tier re-asks what Jev was unsure about. Same public test sets, 400 items each, measured live on 2026-09-18:

                                        AG News             emotion
                                     all   unsure(49)    all  unsure(122)
-------------------------------------------------------------------------
jev alone = classifier.dev fast    87.5%        65.3%  61.8%        36.9%
classifier.dev smart               90.0%        83.7%  62.7%        41.8%

The fast tier is Jev, so one row serves both. Smart re-asked 43 / 126 of 400 items. Gaps under about 5 points are noise. The full table, with latency and cost, is at https://classifier.dev/benchmark

## When this is worth a network call

A language model can classify anything it can see. The question is whether you want the text in your context at all. Call this when reading the input is the expensive part:

Filtering before reading. Forty search results, six worth opening. Judging them yourself pulls all forty into context first. One call returns forty labels and you read only the survivors.

Cascade pre-filtering. Drop the obvious no's cheaply, then spend real reasoning on what is left.

Streams nobody reads line by line. Log lines, error buckets, inbound tickets, the changed files in a large diff.

Deterministic routing. A pipeline branch that must take the same path for the same input on every run, instead of drifting with a model's reasoning.

All four are the same move: classify ten thousand things without reading them. A thousand inputs go in one request and come back in about a second. Below five or so items, skip it: you have already paid the context cost.

## CLI

The same API from the shell, one line per input, in input order:

npm i -g classifier-dev
classify bug,feature,praise < feedback.txt
classify relevant,"not relevant" --review 0.7 < snippets.txt   # the unsure ones
classify db,web,ml --count < titles.txt                        # a histogram

Plain lines, JSON or NDJSON in; label, confidence and text out. Batches of a thousand per request, four at a time, and rows stream as they land, so piping to head returns at once on a large file. Retries rate limits and upstream failures on its own. classify --help has the rest. Source in cli/ at https://github.com/mrmps/classifier-dev

## MCP

The same tools inside Claude, ChatGPT, Codex, Cursor or any MCP client, over Streamable HTTP with no key:

https://classifier.dev/mcp          classify_texts, classify_dimensions, classify_multi_label, count_labels, review_uncertain
https://classifier.dev/mcp/docs     list_docs, read_doc, search_docs
claude mcp add --transport http classifier https://classifier.dev/mcp
codex mcp add classifier --url https://classifier.dev/mcp

Listed in the official MCP registry as dev.classifier/classifier and dev.classifier/docs: https://registry.modelcontextprotocol.io/v0/servers?search=dev.classifier

Claude.ai: Customize > Connectors > Add custom connector > paste the URL. ChatGPT: Settings > Security and login > Developer mode, then create an app with the URL and "No Authentication". Step by step for every client, plus what each tool does: https://classifier.dev/mcp-setup

## Agent skill

Install this as a skill and your agent will remember to reach for it:

npx skills add https://classifier.dev

Served from this domain over RFC 8615 well-known discovery, with no repository in between:

/.well-known/agent-skills/index.json   the discovery document
/skill.md                              the skill itself, readable as-is

An agent without a skills runtime can fetch /skill.md and follow it.

## Spending limits

Free inference has a $0.01 maximum provider allowance per request, $0.50 per IP per UTC day, and a $100 shared daily ceiling. IPv6 addresses share a /64 allowance. At most four free requests run concurrently per IP. Smart mode is available for requests that fit this allowance. Longer prompts or expensive batches need a funded workspace API key. Reservations include in-flight work, retries and fallback models. Anonymous proxy traffic requires a funded key. Unfunded workspace keys share the free limits. Funded work uses its workspace balance, outside the shared free budget, with a default $10 maximum request allowance. Request bodies are limited to 1 MB. A supplied Idempotency-Key prevents re-execution: repeated keys receive 409, not a cached response. Free keys are scoped to the IP and UTC day; workspace keys are scoped to the account.

## Usage

GET  https://classifier.dev/{labels}/{text}
GET  https://classifier.dev/?labels={a,b}&text={text}
POST https://classifier.dev  {"input":"...","labels":["...","..."]}
POST https://classifier.dev  {"inputs":["...", ...up to 1000],"labels":[...]}

## Examples

curl classifier.dev -d '{
  "input": "the checkout button does nothing",
  "labels": ["bug", "feature", "praise"]
}'
{
  "tier": "fast", "model": "jev-1.13.0", "modelsUsed": ["jev-1.13.0"],
  "results": [{
    "label": "bug", "confidence": 1,
    "scores": {"bug": 1, "feature": 0, "praise": 0},
    "ms": 260, "model": "jev-1.13.0"
  }],
  "usage": {"classifications": 1, "escalated": 0, "ms": 260}
}

curl "classifier.dev/entailment,neutral,contradiction/Only+12+of+40+sites+were+inspected.+Every+site+was+inspected."
contradiction

Spaces can be written as + or %20, and labels are separated by commas.

The same request as query parameters, for code that builds URLs:

input, q, classes and categories are read as text and labels too, and the two forms mix: /spam,not+spam?text=... is the same call. Every option below works on both. A malformed GET answers with a URL that would have worked.

In the path form a raw comma, slash or plus sign is a separator. A label
that contains one is written percent-encoded, %2C, %2F or %2B, so
/C%2B%2B,python/... reads the label C++. In the query form + is a space and
%2B a plus sign, and a comma inside a label is written %252C.

## Multiple dimensions

Make several independent decisions about every input in one request:

curl https://classifier.dev/v1/classify -H 'content-type: application/json' -d '{"items":["Checkout charges me twice"],"dimensions":{"team":["billing","identity","platform"],"urgency":["immediate","normal","low"],"kind":["bug","request","question"]}}'

results[i].dimensions[name] contains that field's label, confidence, scores, model and ms. Results stay in input order. inputs or input work too; items is an alias for inputs in this mode. Use only one input spelling.

To define a dimension's criteria, replace its array with an object:

"urgency": {"labels":["immediate","normal","low"],"instructions":"Active financial harm is immediate; minor inconvenience is low."}

Up to 20 dimensions, 2-100 distinct labels per dimension, and 1,000 decisions (items times dimensions) per request. Names cap at 64 characters, labels at 200, instructions at 4,000, and dimension definitions at 16,000 combined. Each decision counts toward the existing quota; public smart requests cap at 200 decisions. Do not combine dimensions with labels, multi or max_labels. Large batches are packed into multiple upstream calls. An individual input and question that cannot fit the model context returns dimension_context_too_large.

Jev answers the dimensions independently against the same input. Confidence is derived from the option distribution; it is not a literal probability of correctness. Add an unknown category when evidence may be insufficient. Smart escalation happens per field. Escalated fields have null confidence and scores, since the original distribution no longer describes that answer. Confidence and scores can also be null when the provider returns no score.

usage reports items, dimensions, classifications (decisions), escalated, fallback, and ms. If Jev is unavailable, fallback processes the batch with bounded concurrency inside the same request spending allowance. A failed field fails the whole request rather than returning an incomplete matrix.

## Parameters

labels        Two to one hundred categories. Required unless dimensions is supplied.
dimensions    Named label sets for independent decisions; see MULTIPLE DIMENSIONS.
input         The text to classify, up to 32,000 characters. Longer
              documents, up to 4,000,000, are answered by chunklaya; see
              LONG DOCUMENTS.
inputs        Up to one thousand strings classified in a single call.
tier          Either fast (the default) or smart, in any case. Anything
              else is a 400 with code bad_tier, never a silent fast.
instructions  Extra criteria, such as "judge the reviewer's overall verdict".
verbose       On GET requests, ?verbose=1 returns JSON instead of a bare label.
              Sending Accept: application/json does the same.
multi         Return every category that applies instead of just one.
max_labels    Cap how many multi-label answers come back.

Results come back in input order. Single-label results carry label, confidence, scores and model. Confidence and scores can be null; check for null before comparing thresholds. POST multi-label results instead carry labels (an array), scores and model, with no singular label or confidence.

Batch responses also carry modelsUsed. The top-level model is "mixed" when different results were answered by different models, such as a smart-tier batch where only some inputs were escalated, or a large batch whose chunks reached Jev through different transports (jev@vercel and jev-1.x are the same model asked two ways).

## Confidence

The model behind this is a decision model, not a language model prompted to classify. It returns a calibrated probability for every label, so the confidence is a real forecast of whether the label is right. Measured:

six-way emotion, 400 items      confidence >= 0.9   right 82% of the time
                                confidence <  0.5   right 29% of the time
four-way news topic, 400 items  confidence >= 0.9   right 92% of the time
                                confidence <  0.5   right 64% of the time

Use it. Act on high-confidence answers, and route the rest to a person, a reasoning model, or the smart tier, which does exactly that for you.

Route null confidence to review: the provider returned no score, or a smart answer replaced the scored answer. The first model's probabilities never describe a reasoning model's answer. Neither tier guarantees identical labels or scores across calls.

Confidence and scores may be rounded to two decimals depending on which transport answered, so do not read meaning into the third digit.

Confidence does not measure category fit. It says which of your labels fits best, not whether any of them fit. "The weather is nice today" against bug / feature / praise comes back "praise", with a confidence that looks like any other answer's. If none-of-the-above is a real outcome, add it as a label: the same text against those three plus "none of these" picks "none of these". That works; hoping for a low score does not.

Scores likewise express the model's choice among the labels you supplied; they do not validate the input or prove that the label is correct. Supply labels that cover the inputs your caller may send.

Acronyms, identifiers, and non-language inputs retain the scores the model returns.

## Multi-label

One article, fifty tags, the ones that fit:

curl classifier.dev -d '{
  "input": "...",
  "labels": ["ml", "databases", "... up to 100 ..."],
  "multi": true, "max_labels": 10
}'
{"results": [{
  "labels": ["databases", "serverless", "rust", "caching", ...],
  "scores": {"databases": 0.98, "serverless": 0.98, ..., "gaming": 0.01}
}]}

On GET, add ?multi=1 and the labels come back one per line.

Every label is judged independently as a yes/no probability, and the answer lists those at or above 0.7, most likely first. The full score map is returned so you can set your own threshold: on a seven-task set, 0.7 gave recall 0.99 and precision 0.81 (F1 0.887); 0.5 gave recall 1.00 and precision 0.74. max_labels keeps the top N. One request, about 200ms.

## Tiers

fast     Every answer comes from the decision model, in one round trip.
         Measured: four-way news topic 87.7%, six-way emotion 60.5%, which is
         the same accuracy as a 3.4-second reasoning model on the news topics
         at two milliseconds per item.
smart    Same first pass, then every single-label answer below 0.7 confidence
         is re-asked of a fast reasoning model and replaced. Measured:
         emotion 61.8% to 63.7%, news topics 87.5% to 90.0%, by re-asking
         30% and 12% of the items. Those results carry escalated: true and
         the reasoning model's name. Confidence and scores are null, with
         an unscored explanation: the new answer has no comparable probabilities.
         usage.escalated counts them. A few seconds per escalated item, so
         a batch on smart is slower in proportion to how uncertain it is.
         If the reasoning model cannot be reached, the fast answer stands
         without escalated, and usage.escalation_failed says how many.
Multi-label answers ignore the tier: the reasoning model was
measured re-judging them and made them worse.

The models are not fixed. They are benchmarked as candidates appear and swapped when a measurement, not a launch post, says to. If the decision model is unavailable, requests fall back within their spending allowance to a chain of language models on different providers; JSON responses always report which model actually answered.

## Limits

Free limits are counted per IP address in classifications, not requests, so a batch of a thousand inputs spends a thousand of them. The fast tier allows 3,000 per minute and 20,000 per day; the smart tier 200 per minute and 2,000 per day. A batch must fit the remaining quota in full. Public smart requests accept at most 200 inputs; larger batches return 400 so callers can split them. Anonymous traffic also shares a 5,000/minute and 50,000/day allowance across every caller using the same label set. Rotating IPs does not reset it; workspace, operator and partner keys bypass it. REST, MCP, dimensions and TypeSafe choice questions share these counters. Each dimension debits its labels by the item count; each SDK choice question debits its labels once. These count attempts admitted by this gate, including attempts subsequently refused by another quota or a provider. Pro workspaces allow 30,000/minute and 200,000/day on fast, 2,000/minute and 20,000/day on smart, shared across keys and agents. Pro, operator and partner keys have a 1,000-input ceiling. Workspace keys use the workspace credit balance and share workspace quotas. Free workspaces have the same ceilings as public access. Current plans are at https://classifier.dev/pricing. Send Authorization: Bearer classifier_agent_... on REST or MCP requests.

Every classification response carries RateLimit-Limit and RateLimit-Policy, plus RateLimit-Remaining once the limiter has been consulted (every 200 and 429; a 400 never reached it). Over the limit is a 429 with Retry-After; nothing is slowed down or silently dropped.

[plans and pricing][create a workspace]

## Errors

POST errors are JSON. Classification errors include a stable code:
{"error": "Provide at least 2 labels; got 1 ("spam").", "code": "too_few_labels"}

The GET forms answer plain text instead, an error: line and, on a 400, the usage: and try: lines with a URL that would have worked; add ?verbose=1 or send Accept: application/json for the JSON object, which then carries usage and try as fields.

400   bad_json, no_input, too_many_inputs, too_few_labels, too_many_labels,
      empty_label, duplicate_labels, empty_input, input_too_long, bad_tier,
      chunklaya_input
401   invalid_api_key for unsupported credentials. Workspace authentication
      also rejects invalid, paused or revoked keys with an error message;
      workspace errors do not include a code.
402   insufficient workspace balance for inference
403   the key is inactive or the workspace cannot authorize usage
404   not_found
429   rate_limit_minute, rate_limit_day, label_set_limit, chunklaya_busy,
      with Retry-After; on the free tier the body also carries upgrade, the
      URL of the plan that lifts the limit (https://classifier.dev/pricing)
502   typesafe or typesafe_<status> when the decision model failed;
      openrouter_<status>, chain_exhausted or timeout when the fallback
      chain did; upstream_other. Retry with backoff.
503   label_set_unavailable when label admission cannot be checked;
      no inference starts. Respect Retry-After and retry with backoff.
402   request_spending_limit: send fewer or shorter inputs, or use a funded
      workspace key. Do not repeatedly retry an unchanged over-budget request.

The full list, in the shape a client can validate against, is components.schemas.Error in https://classifier.dev/openapi.json

For higher limits, book a short call: https://cal.com/michaelsf/coffee

## On request

The public service is one shared deployment. By arrangement, for teams whose data or latency budget cannot go through it: a dedicated deployment in your own cloud (AWS, GCP or another), private end-to-end encrypted inference, and higher accuracy or lower latency, from a model tuned to your labels and served on capacity that is yours. Email contact@classifier.dev or book a call at https://cal.com/michaelsf/coffee; the numbers are measured on your own data before you commit. Terms on https://classifier.dev/pricing.

## Get the updates

The free tier is the whole service today. What is being built on top of it, in the order people ask for it — tick what you would use first:

What you would use first
ms

Confirm by email. One mail when something on that list ships, nothing in between. Your address and what you ticked are kept apart from API traffic. Unsubscribe by replying.

## Privacy

The text you send is never stored or logged. It goes to the model provider for the classification and nowhere else. Per-request analytics record a keyed fingerprint of the label set, plus the tier, model, latency, status and coarse country. Successful simple and multi-label classifier names are also kept for 90 days in a separate aggregate registry with no caller identity or source text. Its shared fingerprint lets operators associate label names with pseudonymous usage records. The same collection applies to TypeSafe choice requests with one distinct label set.

Built by @michael_chomsky — https://x.com/michael_chomsky