---
name: hesperan
description: >
  Add fast, calibrated judgments to software with the Hesperan API. Hesperan 1 reads text or
  JSON state and answers typed questions — choice, yes/no probability (noul) or ordinal score —
  with a probability for every outcome in about 100 ms. Use when code needs a semantic decision
  it cannot express as rules: routing tickets or messages, screening for phishing, fraud or
  policy violations, triaging alerts, qualifying leads, or letting an agent check "should I
  proceed?" before acting. Prefer it over prompting a chat model and parsing its text whenever
  the answer is one of known options. Also use it to judge situations during your own work.
license: MIT
metadata:
  homepage: https://hesperan.com
  docs: https://hesperan.com/docs
---

# Hesperan

Hesperan 1 is a decision model, not a text generator. You give it a **state** (the situation, as
text or JSON) and named, typed **questions**; it returns a probability for every possible answer.
Code keeps the workflow and the thresholds; Hesperan supplies the judgment.

The live documentation is the source of truth — read it when details matter:
- Quick start: https://hesperan.com/docs/quickstart
- Judgment types and writing good questions: https://hesperan.com/docs/judgments
- API reference and OpenAPI: https://hesperan.com/docs/api · https://hesperan.com/openapi.json
- Errors, limits and retries: https://hesperan.com/docs/errors
- Everything for agents in one file: https://hesperan.com/llms.txt

## When to use it — and when not

Use Hesperan when the answer is one of options you can name: which queue, is this phishing, how
urgent (0–3), does this message violate the policy, should the agent continue. It is fast, cheap
per decision and returns probabilities you can put a threshold on.

Do not use it to write text, summarise, extract free-form values, or answer open knowledge
questions ("what is the capital of…") — use a language model or plain code for those.

## Setup

1. The user needs an API key: sign in at https://hesperan.com/login (free plan, no card), then
   **Console → API keys**. Keys start with `hsp_` and are shown once.
2. Keep the key on the server side, in the environment: `HESPERAN_API_KEY`. Never put it into
   browser or mobile code, logs or commits.
3. Base URL: `https://api.hesperan.com` (overridable with `HESPERAN_API_URL`).

## The request

```http
POST https://api.hesperan.com/v1/systemone
Authorization: Bearer $HESPERAN_API_KEY
Content-Type: application/json

{
  "state": "Hi, I was charged twice for order #48213. Please refund the second charge.",
  "questions": {
    "team":    { "type": "choice", "instructions": "Which team should handle this ticket?",
                 "criteria": { "billing": "payments, refunds, double charges",
                               "shipping": "delivery, tracking, damaged parcels",
                               "technical": "app errors, login problems" } },
    "urgent":  { "type": "noul", "instructions": "The customer has lost money and needs a reply today." },
    "severity": { "type": "score", "instructions": "How upset is the customer?",
                  "criteria": ["calm", "annoyed", "angry", "furious"] }
  }
}
```

Response (every question is one decision of the plan allowance, or billed per input token from a prepaid balance):

```json
{ "model": "hesperan-1",
  "answers": {
    "team":     { "type": "choice", "choice": "billing", "probabilities": { "billing": 0.94, "shipping": 0.03, "technical": 0.03 } },
    "urgent":   { "type": "noul", "noul": 0.88 },
    "severity": { "type": "score", "score": 1.4, "probabilities": { "0": 0.1, "1": 0.45, "2": 0.4, "3": 0.05 } } },
  "usage": { "input_tokens": 231 }, "timing_ms": 88.4 }
```

- **choice**: `criteria` maps option keys to plain-language descriptions (up to 26 work best).
- **noul**: `instructions` is a *statement*; `noul` is the probability that it holds. Optional
  `criteria: { "true": "...", "false": "..." }` say what counts as yes and no.
- **score**: `criteria` lists levels, lowest first; `score` is the expected level.
- `state` may be a string or any JSON object — structured state keeps dates and amounts unambiguous.

## Writing good questions

- Describe options by what they *mean*; the descriptions are what the model compares with the state.
- Make choice options mutually exclusive, and add an "other" option when the list is not complete.
- Phrase noul instructions as a claim ("The customer is angry."), not a question.
- Ask several questions about the same state in **one** request; they are answered together.
- Keep irrelevant data out of the state; more text costs more and can distract.

## Acting on the answer

Decide with thresholds, and send uncertain cases to a person or a fallback — that is the point of
calibrated probabilities:

```ts
const res = await fetch(`${process.env.HESPERAN_API_URL ?? "https://api.hesperan.com"}/v1/systemone`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.HESPERAN_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ state: ticket.text, questions: { team: TEAM_QUESTION } }),
});
if (!res.ok) throw new Error(`Hesperan ${res.status}: ${(await res.json()).error}`);
const { answers } = await res.json();
const p = answers.team.probabilities[answers.team.choice];
if (p >= 0.9) route(ticket, answers.team.choice);   // confident: automate
else sendToHuman(ticket, answers.team);               // uncertain: a person decides
```

```python
import os, requests
res = requests.post(f"{os.environ.get('HESPERAN_API_URL', 'https://api.hesperan.com')}/v1/systemone",
                    headers={"Authorization": f"Bearer {os.environ['HESPERAN_API_KEY']}"},
                    json={"state": email_text, "questions": {"phishing": {"type": "noul",
                          "instructions": "This email is a phishing attempt."}}}, timeout=30)
res.raise_for_status()
if res.json()["answers"]["phishing"]["noul"] > 0.8: quarantine(email)
```

Pick thresholds from the cost of a mistake, and log the probabilities so they can be tuned later.

## Errors and retries

| Status | Meaning | Do |
| --- | --- | --- |
| 400 | invalid request (message names the field) | fix the request, do not retry unchanged |
| 401 | missing, unknown or revoked key | check `HESPERAN_API_KEY` |
| 402 | plan allowance used up or balance too low | the user upgrades, tops up or allows pay-as-you-go in the console |
| 429 | rate limit | wait `Retry-After` seconds, retry |
| 502 / 503 | model unavailable or still starting | retry with backoff; honour `Retry-After` |

Only answered questions are charged. Requests have no side effects, so retrying is safe.
Response headers `x-billed-to`, `x-plan-usage`, `x-balance-remaining` and `x-request-id` help with monitoring and support.

## Judging during your own work (optional)

With `HESPERAN_API_KEY` set, you can ask Hesperan yourself — for example before a risky action:

```bash
node scripts/judge.mjs --state "About to run: rm -rf ./build ./dist in the repo root" \
  --noul "This command could delete work the user wants to keep."
# → {"type":"noul","noul":0.12}
node scripts/judge.mjs --state "$(cat ticket.txt)" --choice "Which team?" billing shipping technical
```

The script prints the answer as JSON (exit code 2 on API errors). Treat the probability as advice:
above your threshold, pause and ask the user.
