Haiku Decision API

An unofficial prototype of a typed-decision API on Claude Haiku 5.5. Not an Anthropic product.

See It in Action

Introduction

A decision API that answers in one word. It's live, not just a demo.

Some questions about text need a typed answer, not a paragraph: is this email spam, which team should handle this ticket, how urgent is it. Decision APIs such as TypeSafe's Jev return exactly that: a true or false, one of your options, or a level on your scale.

Haiku Decision API is a working prototype of that idea built on Claude Haiku 5.5, with nothing added but configuration. It takes Jev's request shape, asks Haiku each question with effort set to low and thinking turned off, and accepts the reply only if it exactly matches one of your labels. Anything else comes back as an error for that question, never as a guess.

It is free to call, with no key, within a small daily budget. It returns decisions only, without probabilities. The code is open source under the Apache License 2.0 on GitHub.

Request

POST /haiku-decision-api/v1/systemone
{
  "model": "haiku-decision-2026-10-22",
  "state": {"subject": "Invoice overdue", "body": "Hi, your invoice #4411 is three weeks late..."},
  "questions": {
    "is_spam": {"type": "noul", "instructions": "Is this email spam?"},
    "team": {"type": "choice", "instructions": "Which team should handle it?",
             "criteria": {"Billing": "Payments, invoices, refunds", "Technical": null}}
  }
}

Response

{
  "model": "haiku-decision-2026-10-22",
  "answers": {
    "is_spam": {"type": "noul", "decision": false},
    "team": {"type": "choice", "choice": "Billing"}
  },
  "usage": {"input_tokens": 212, "output_tokens": 2},
  "latency_ms": 734,
  "no_probability_notice": "This API returns decisions only. …"
}

The response figures in this example are illustrative.

Performance & Cost

What turning thinking off buys, and what it costs.

We sent the same 1,000 emails from the SpamAssassin public corpus (half spam, half not spam) through this API twice: once with its own settings, once with Haiku's default settings. Same prompt, same question ("Is this email spam?"), one request at a time, alternating which configuration went first.

Benchmark through this API, 1,000 emails per setting, 2026-10-22
Haiku DecisionHaiku 5.5, default settings
Median latency0.663 s0.819 s
95th percentile1.426 s2.049 s
Mean0.775 s1.006 s
Accuracy96.5%98.2%
Spam caught98.4%98.2%
Not-spam kept94.6%98.2%
Hard not-spam kept67.9%89.3%
No answer0.1% (1)0.0% (0)
Input tokens per call (mean)608.7609.7
Output tokens per call (mean)3.043.3
Cost per call$0.0000624$0.0000826
Cost of all 1,000 calls$0.0624$0.0826
  • Speed: the median call is 1.23× faster (0.663 s against 0.819 s).
  • Quality is the cost of speed, not a score to beat. With thinking off, Haiku decides on its first read. It lost the most on the corpus's "hard ham": legitimate mail that looks like marketing. 9 of those 28 went to SPAM, against 3 with default settings.
  • No answer: 1 of Haiku Decision's 1,000 replies was neither true nor false: Haiku began describing the email instead and ran out of its 16 tokens. The API returned an invalid_output error for it, never a guess, and it counts as not correct. Default settings: 0. In three later tries on that email, lite answered wrongly twice and described it once.
  • Cost: prices are Anthropic's published Haiku 5.5 rates, $0.10 per million input tokens and $0.50 per million output tokens (retrieved 2026-10-22). Both configurations read the same input; the default settings also write thinking on many calls.

Measured on 2026-10-22 from Chicago over Wi-Fi. Each request went to this API's own code running on the same Mac, so the times include about 3 ms of API overhead but not the trip to our server in Iowa. Times are wall clock around each request on the client, one request at a time, after one throwaway warm-up call per setting; no retries in any figure. An earlier run the same day, calling Haiku directly without this API, gave medians of 0.751 s and 0.914 s (1.22×). Your latency depends on where you are.

Through the public endpoint

We also sent the same 1,000 emails to the public address, nguyennhattran.com/haiku-decision-api/v1/systemone, with this API's settings only (Haiku's default settings are not served publicly). Measured on 2026-10-22 from Chicago over Wi-Fi, one request at a time, after one warm-up request.

Haiku Decision through nguyennhattran.com, 1,000 emails, 2026-10-22
Median, as the caller waited0.721 s
95th percentile, as the caller waited1.774 s
Mean, as the caller waited0.912 s
Median latency_ms reported by the API0.652 s
Accuracy96.8%
No answer0.0% (0)

The gap between the caller's median and the API's own latency_ms (69 ms) is mostly the network: from Chicago through Google's front end and our gateway to the server in Iowa, and back.

Quality by corpus folder

Accuracy by corpus folder
FolderHaiku DecisionHaiku 5.5, default settings
easy_ham94.7%98.0%
easy_ham_298.8%100.0%
hard_ham67.9%89.3%
spam98.7%98.7%
spam_298.3%98.0%

Live runs, last 30 days

From the Run it live button, shown apart from the benchmark. Loading this tally wakes the API.

What others say about speed

Vendor claims, not measured by this project:

  • OpenAI states its Decisions API returns typed answers "about 10x faster than the Responses API" (OpenAI Decisions guide, retrieved 2026-10-22).
  • TypeSafe states Jev responds in "70ms-500ms" end to end (TypeSafe launch post, retrieved 2026-10-22).

These are different kinds of statements from ours (a ratio with no baseline figures, and a range with no percentile or location), so they are not a like-for-like comparison.

Limitations

  • Unofficial. A personal prototype built on the public Claude API. Not an Anthropic product, not endorsed by Anthropic or TypeSafe.
  • No probabilities. Responses carry decisions only, because none have been measured or calibrated here. Why.
  • Quality has a cost. Thinking is off, so hard cases fare worse (see Performance & Cost). Check it against your own data before relying on it.
  • Thinking is off, not just turned down. Anthropic's guidance names effort as the main lever for trading quality against speed. We lower effort and also turn thinking off. With thinking on, Haiku may still write a reasoning step before it answers, even at low effort. With it off, every call answers straight away. That fits what this API is for: a quick gut check on a piece of text, the same job Jev does, not long multi-step reasoning. (We benchmarked these settings against Haiku's defaults, not against low effort with thinking on.)
  • The benchmark corpus is old and public. The SpamAssassin corpus dates from 2002 to 2005 and is very likely in the model's training data. Treat the accuracy figures as a sanity check, not a forecast.
  • Latency depends on location. Our figures are from Chicago. Every request also passes through a gateway and a server in Iowa (us-central1).
  • Small daily budget, cold starts. The API is free within $0.50 of model spend per day (UTC). After that it returns 429 until midnight UTC. It sleeps after about 15 minutes without requests, and waking takes 5 to 50 seconds (Cold starts). Before a batch, send GET /health and wait for its answer.
  • Text only. No images, no assets, no box questions.
  • Data. Request content is never stored by this API. Requests are sent to Anthropic's API and handled under Anthropic's standard API data policy. Google Cloud's request log records each request's IP address and path.
  • Corpus licence. The SpamAssassin public corpus carries no licence grant. Copyright in the messages stays with their original senders. The demo shows short excerpts for demonstration only, with addresses masked. Emails whose excerpt contains explicit words are never displayed, in the waterfall, the replay or live runs; they are still in every count and figure (8 of the replay's 100 sampled emails).

Getting Started

No key, no signup. Send a POST with JSON.

To run your own copy, the full source is on GitHub.

Cold starts: the API sleeps when idle and takes up to about a minute to wake. Call GET /health first and wait for its answer.

curl

curl https://nguyennhattran.com/haiku-decision-api/v1/systemone \
  -H "Content-Type: application/json" \
  -d '{
    "model": "haiku-decision-2026-10-22",
    "state": "Your order #1182 has shipped and arrives Friday.",
    "questions": {
      "is_spam": {"type": "noul", "instructions": "Is this message spam?"},
      "urgency": {"type": "score", "instructions": "How urgent is it?",
                  "criteria": ["Can wait", "This week", "Today"]}
    }
  }'

Python (standard library only)

import json
import urllib.request

body = {
    "model": "haiku-decision-2026-10-22",
    "state": {"subject": "Charged twice", "body": "My card was billed twice for March."},
    "questions": {
        "team": {"type": "choice", "instructions": "Which team should handle it?",
                 "criteria": {"Billing": "Payments and refunds", "Technical": None}},
    },
}
request = urllib.request.Request(
    "https://nguyennhattran.com/haiku-decision-api/v1/systemone",
    data=json.dumps(body).encode(),
    headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request) as response:
    answer = json.load(response)["answers"]["team"]
print(answer.get("choice") or answer["error"])

JavaScript

const response = await fetch("https://nguyennhattran.com/haiku-decision-api/v1/systemone", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "haiku-decision-2026-10-22",
    state: "Congratulations! You have won a free cruise. Click to claim.",
    questions: { is_spam: { type: "noul", instructions: "Is this message spam?" } },
  }),
});
const { answers } = await response.json();
console.log(answers.is_spam.decision ?? answers.is_spam.error);

Always check each answer for error: a question can fail while the others succeed.

Docs

Everything you need to call the API, on this page.

Endpoint

POST https://nguyennhattran.com/haiku-decision-api/v1/systemone
Content-Type: application/json

No trailing slash. JSON in, JSON out. No authentication. Any origin may call it from a browser (CORS open).

Request

FieldTypeNotes
modelstring, requiredMust be haiku-decision-2026-10-22.
statestring, object or array, requiredThe content every question is about. Objects and arrays are sent to the model as JSON. Up to 16,000 characters (objects and arrays measured as compact JSON).
questionsobject, required1 to 10 questions, keyed by IDs you choose (up to 64 characters). IDs are never shown to the model; answers come back under the same IDs.

Each question:

FieldTypeNotes
type"noul", "choice" or "score"
instructionsstring, object, array or nullThe question. Optional for choice and score; a noul needs instructions or criteria.
criteriadepends on typeSee below. instructions plus criteria up to 4,000 characters per question.

Question types

TypecriteriaAnswer
noul (yes or no)Optional object with keys true and/or false, each a description of when that answer applies.{"type": "noul", "decision": true}
choice (pick one)Object of 1 to 255 options: option name → description, or null for no description. Order is kept.{"type": "choice", "choice": "<option name>"}
score (a level on a scale)Array of 1 to 10 level descriptions, lowest first.{"type": "score", "level": 1, "legend": {"0": "…", "1": "…", "2": "…"}}. Levels count from 0.

Response

FieldNotes
modelhaiku-decision-2026-10-22
answersOne entry per question ID, in your order. Each has type and either its answer field or error.
usageinput_tokens and output_tokens, summed over all questions.
latency_msTime this API spent waiting on the model: from the first call starting to the last one returning. Questions run in parallel. Network time to and from you is not included.
no_probability_noticeA one-sentence notice with a link to Why there are no probabilities.

There are no probability, confidence or expected-value fields: they are absent, not null.

How answers are checked

Each question is its own call to Haiku, which must reply with a single label: true or false, an option number, or a level number (the API numbers your options and levels and maps the reply back). Surrounding whitespace is trimmed; anything else, including a different case, a trailing period or an extra word, is rejected. A rejected reply becomes an invalid_output error for that question, never a guess. This also keeps the endpoint from working as a general-purpose chat proxy.

Errors in an answer (HTTP 200)

error.codeMeaning
invalid_outputThe model's reply did not match one of your labels exactly.
refusalThe model declined to answer.
upstream_errorThe model call failed or took longer than 10 s. There are no automatic retries; try again.
"team": {"type": "choice", "error": {"code": "invalid_output", "message": "Model output did not match an option number."}}

Errors for the whole request

HTTPBodyWhen
413{"error": {"code": "body_too_large", "message": "…"}}The body is over 256 KiB.
422{"detail": [{"loc": […], "msg": "…", "type": "…"}]}A field is missing, has the wrong type or breaks a limit. loc names the field. Nothing is ever trimmed silently.
429{"error": {"code": "rate_limited", "message": "…", "retry_after_s": 12}}More than 100 requests in a minute from your address. Retry-After header in seconds.
429{"error": {"code": "budget_exhausted", "message": "…", "reset_at": "2026-10-23T00:00:00Z"}}Today's budget is used up. Retry-After header until the reset.
503{"error": {"code": "unavailable", "message": "…"}}The API cannot reach its database, so it cannot track the budget. Try again later.

Limits

LimitValue
Request body256 KiB
state16,000 characters
Questions per request10
instructions + criteria, per question4,000 characters
Choice options1 to 255
Score levels1 to 10
Question ID64 characters
Model call timeout10 s per question
Imagesnot supported

Rate limits and pricing

  • Free to call. No key, no account.
  • Daily budget: $0.50 of model spend per UTC day for the public API (about 8,000 one-question calls at the benchmark's average email size), counted from Anthropic's published prices and each call's returned usage. When it runs out, every request returns 429 budget_exhausted until 00:00 UTC. The demo's live runs have their own separate $0.50 budget.
  • Per address: 100 requests per minute. Bursts are fine; the budget is the hard limit.
  • Live runs (the demo button): 3 per minute and 20 per hour per address, one at a time.

Cold starts

The API runs on Google Cloud Run and scales to zero: about 15 minutes after the last request it shuts down, so it costs nothing while idle. The next request starts it again and waits until it's ready. In our measurements that took 5 to 35 seconds after a short quiet spell and up to about 50 seconds after hours of quiet (Cloud Run's logs, 2026-10-22 to 2026-10-23). Google doesn't guarantee these times. latency_ms never includes the wait, and neither does any figure on this page.

Only one instance runs at a time. Requests that arrive while it starts may be refused by Cloud Run itself with 429, 500 or 503 and a plain-text body. This API's own errors always come as JSON with error.code. Before a batch, send GET /health (no model call, nothing spent) with a timeout of at least 90 seconds, wait for its answer, then send the rest. Don't retry in parallel while it wakes.

Model and settings

Every request is one call per question to Anthropic's Messages API with exactly these settings:

SettingValue
Modelclaude-haiku-5-5
Effortlow (output_config.effort)
Thinkingdisabled
max_tokens16
temperature, top_p, top_knot sent (Anthropic's defaults)
Retriesnone
Timeout10 s

The model name haiku-decision-2026-10-22 stands for exactly this configuration and will not change meaning. The benchmark's comparison configuration, "Haiku 5.5, default settings", is effort medium with adaptive thinking and max_tokens 4096; it is used only in the demo, not served by the API.

Why there are no probabilities

This API returns decisions only. Probabilities are omitted because none have been measured or calibrated.

Jev's answers carry probabilities: a noul's noul field is the probability of yes, a choice has confidence and probabilities, and a score's score is an expected value across levels. This prototype leaves all of those out entirely rather than sending null or a placeholder, so a client that relies on them fails visibly instead of silently comparing against a made-up number. In their place, a noul answers with decision (true or false) and a score with level (one whole level). choice and legend keep Jev's meaning.

Differences from Jev

  • No API key; a daily budget and per-address rate limits instead.
  • No probability fields (above); decision and level replace Jev's noul and score.
  • No images: assets and box questions are rejected.
  • One model name, haiku-decision-2026-10-22, and lower size limits (see Limits).

Source and licence

The code is open source under the Apache License 2.0: github.com/NguyenTran2002/haiku-decision-api. Email excerpts shown in the demo come from the SpamAssassin public corpus and are not covered by that licence.

Data and privacy

  • This API never stores or logs request content, question IDs or your IP address. It keeps daily counts and spend only, deleted after 30 days.
  • Your state and questions are sent to Anthropic's API and handled under Anthropic's standard API data policy.
  • Google Cloud's request log records each request's IP address and path.