API

September 20, 2026 · View on GitHub

evaluate POSTs to TypeSafe https://api.typesafe.ai/v1/systemone (or OpenRouter /api/alpha/decisions when only OPENROUTER_API_KEY is set). Auth: Authorization: Bearer <key>.

Request

{
  "state": "Help! My payouts have been failing for 3 days.",
  "model": "jev-latest",
  "questions": {
    "is_urgent": {
      "type": "noul",
      "instructions": "Does this convey urgency?",
      "criteria": { "true": "Explicitly time-sensitive", "false": "No urgency expressed" }
    },
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "billing": "Payments, invoicing, refunds",
        "technical": "Bugs, outages, integrations",
        "sales": "Pricing, upgrades, new accounts"
      }
    },
    "frustration": {
      "type": "score",
      "instructions": "How frustrated is the customer?",
      "criteria": ["Calm", "Frustrated", "Very angry"]
    }
  }
}
FieldRequiredNotes
stateyesstring | object | array of text
modelAPI yes; evaluate noDefaults to jev-latest, or ~typesafe/jev-latest on OpenRouter. Pass-through if set.
questionsyesMap of id → {type, instructions, criteria?}. Id is not sent to the model.

Response

{
  "model": "jev-1.13.0",
  "answers": {
    "is_urgent": { "type": "noul", "noul": 0.92 },
    "department": {
      "type": "choice",
      "choice": "technical",
      "probabilities": { "billing": 0.08, "technical": 0.85, "sales": 0.07 },
      "confidence": 0.82
    },
    "frustration": {
      "type": "score",
      "score": 1.6,
      "legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" },
      "probabilities": { "0": 0.05, "1": 0.3, "2": 0.65 },
      "confidence": 0.78
    }
  },
  "usage": { "input_tokens": 312, "output_tokens": 48 }
}

response.model is the versioned ID that answered, which may differ from the alias you sent. Log it. Score legend / probabilities keys are stringified 0-based indices.

Errors

StatusMeaning
400{"detail":{"error_type":"max_tokens_exceeded"}} — the request is over the context budget. Refused, never truncated (probed 2026-09-20 at ~192 KB and ~384 KB of state; pinned by TestLiveJevOverBudget). Not retried.
401Missing/invalid key
422Body failed validation (field path in body)
429Rate limit — backoff; honor retry-after
529Overloaded — backoff

evaluate retries 429 and 529 with exponential backoff (up to 3), honoring retry-after (capped at 60s). Other API errors surface as tool errors. Body cap 16 MiB. Timeout 60s.

Retries are invisible from outside the binary, so a caller timing the tool cannot tell a slow answer from a backoff. EVALUATE_CALL_STATS=1 prints one line per call to stderr{"evaluate_call":{"attempts":2,"elapsed_ms":812.4,"ok":true}} — and changes nothing else; the tool result never carries timing (ADR-004 §2). A diagnostic for eval harnesses, off by default.

Also

  • GET /v1/models lists aliases (name, description, release_date). Versioned IDs like jev-1.13.0 are accepted even when not listed.
  • Preview API (POST /preview/evaluation, document, prompts[], probability/chosen/expectation) is gone. v1 only. Migration.
  • OpenAPI: https://api.typesafe.ai/openapi.json (info.version 0.2.0 as of fetch).
  • Python SDK typesafe-sdk; JS @typesafe-ai/sdk. This repo talks HTTP, not those SDKs.

Official: API reference