Migrate from TypeSafe or OpenAI
POST https://api.mouse.dev/v1/decisions (primary) and POST https://api.mouse.dev/v1/systemone (alias) are wire-compatible with TypeSafe's System One API (jev-latest / jev-1.13.0), and also accept OpenAI's Decisions API (POST /v1/decisions on gpt-6-luna) field names directly.
Side-by-side comparison: Mouse vs TypeSafe Jev vs OpenAI Decisions
Section titled “Side-by-side comparison: Mouse vs TypeSafe Jev vs OpenAI Decisions”| Feature / Field | Mouse (mouse-1) | TypeSafe Jev (jev-1.13.0) | OpenAI Decisions (gpt-6-luna) |
|---|---|---|---|
| Endpoint | POST https://api.mouse.dev/v1/decisions (alias /v1/systemone) | POST https://api.typesafe.ai/v1/systemone | POST https://api.openai.com/v1/decisions |
| Model name | "mouse-1" (snapshot mouse-preview-2026-10-13) | "jev-latest" (jev-1.13.0) | "gpt-6-luna" |
| Input / context field | state (string, JSON object, or array) | state (string, JSON object, or array) | input (string or user message array) |
| Questions container | questions object map ({ "<id>": Question }) | questions object map ({ "<id>": Question }) | questions array ([{ "name": "<id>", ... }]) |
| Binary yes/no primitive | type: "noul" → answer.noul (0..1) | type: "noul" → answer.noul (0..1) | type: "predicate" → answer.probability (0..1) |
| Multi-option primitive | type: "choice", criteria: { key: desc } (up to 1,024 options) | type: "choice", criteria: { key: desc } (up to 255 options) | type: "choice", choices: [{ value, description }] |
| Choice probabilities shape | Object map: { "billing": 0.91, "shipping": 0.09 } | Object map: { "billing": 0.91, "shipping": 0.09 } | Array: [{ "value": "billing", "probability": 0.91 }, ...] |
| Ordinal rubric primitive | type: "score", criteria: [lvl0, lvl1, ...] (2–10 levels) | type: "score", criteria: [lvl0, lvl1, ...] (2–10 levels) | type: "score", levels: [{ label, description }] |
| Score probabilities shape | Object map: { "0": 0.1, "1": 0.7, "2": 0.2 } + legend | Object map: { "0": 0.1, "1": 0.7, "2": 0.2 } + legend | Array: [{ "value": 0, "label": "...", "probability": 0.1 }, ...] |
| Confidence formulas | System One choice & score formulas + mouse.p_top | System One choice & score formulas | Undocumented internal confidence scalar |
| Refusal answer type | Never refuses (choice, noul, or score always returned) | Never refuses | Can return {"type": "refusal", "name": "..."} |
| First-party calibration | POST /v1/calibrations → signed calp1. profile + mouse.abstain | None (manual thresholding only) | None (manual thresholding only) |
| Max state / context | 65,536 state tokens (422 on overflow, never truncated) | 32k state+longest question / 64k request | Not published |
| Rate limits | 200 RPS (burst 400), 20M TPM published on every key | 80 RPS / 100K TPS ("adjusting dynamically") | Tier-based RPM |
| Price per 1M tokens | $0.10 input · $0.02 cached (5m) · $0 output | $0.042 input · no cache tier · $0 output | $0.10 input (+10% regional) · no cache discount · $0 output |
Itemized usage & cost_usd | input_tokens, state_tokens, question_tokens, framing_tokens, cached_tokens, cost_usd | input_tokens, output_tokens | Token counts only |
| Zero Data Retention (ZDR) | Default for every customer (Mouse-Data-Retention: none) | Enterprise plan via sales only | Eligible customers with prior approval |
1. Migrating from TypeSafe Jev (POST /v1/systemone)
Section titled “1. Migrating from TypeSafe Jev (POST /v1/systemone)”Because Mouse implements the exact /v1/systemone request, answer, and confidence specification, migrating from Jev requires changing only the base URL and model name:
const res = await fetch("https://api.typesafe.ai/v1/systemone", {const res = await fetch("https://api.mouse.dev/v1/systemone", { method: "POST", headers: { Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`, Authorization: `Bearer ${process.env.MOUSE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "jev-latest", model: "mouse-1", state, questions, }),});Or with @typesafe-ai/sdk / typesafe-sdk:
import { TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient({ apiKey: process.env.MOUSE_API_KEY, baseURL: "https://api.mouse.dev", defaultModel: "mouse-1",});What stays identical vs what improves
Section titled “What stays identical vs what improves”- Identical:
questions(choice,noul,score), structuredinstructionsandcriteria,answers.<id>.choice,answers.<id>.noul,answers.<id>.score,probabilities,legend, and the exactconfidenceformulas. - Additional fields (ignored by existing SDKs): Each answer includes
logitsandmouse: { calibrated, p_top, abstain, confidence_winning, confidence_normalized }(noulanswers also gainprobabilities), andusageincludesstate_tokens,question_tokens,framing_tokens,cached_tokens, andcost_usd. - Re-calibrating thresholds & shadow replay: Even though
confidenceuses the same formula overprobabilities,mouse-1andjev-1.13.0have different underlying model weights. A threshold tuned on Jev does not transfer. Replay your existing Jev or OpenAI JSONL logs withapps/decide/scripts/shadow_migrate.py(MOUSE_API_KEY=sk_mouse_... python scripts/shadow_migrate.py logs.jsonl --calibrate) to inspect top-1 agreement rate, confidence shift, 10-bin ECE, and fit a temperature and an out-of-fold cutoff formouse-1viaPOST /v1/calibrations, then branch onmouse.abstain. - Option order: If you saw answers flip when options were reordered, pass
order_invariant: trueand Mouse averages two passes server-side.
2. Migrating from OpenAI Decisions (POST /v1/decisions)
Section titled “2. Migrating from OpenAI Decisions (POST /v1/decisions)”OpenAI's Decisions API uses the same three conceptual primitives under slightly different field names:
- Change the base URL to
https://api.mouse.dev(POST /v1/decisionsis served) and"model": "gpt-6-luna"to"model": "mouse-1". - Send the request body unchanged. Mouse reads the OpenAI field names as aliases (
input→state; thequestionsarray keyed byname;predicate→noul;choices: [{ value, description }]andlevels: [{ label, description }]→criteria). The canonical map form below is what the request becomes; you can convert at your own pace. - Read the Mouse response shape:
answersis a map keyed by your questionnames, apredicatecomes back asanswers.<name>.noul(notanswers[i].probability), andchoiceprobabilities are a map (answers.<name>.probabilities[value]). - Remove
answer.type === "refusal"branches—mouse-1never refuses a valid schema and always returns a probability distribution over your options.
Before (OpenAI POST /v1/decisions)
Section titled “Before (OpenAI POST /v1/decisions)”{ "model": "gpt-6-luna", "input": "I was charged twice for my order.", "questions": [ { "type": "choice", "name": "department", "instructions": "Which department should handle this complaint?", "choices": [ { "value": "billing", "description": "Payments, invoices, and refunds." }, { "value": "technical", "description": "Problems using the product." }, { "value": "shipping", "description": "Delivery and tracking." }, { "value": "other", "description": "Requests outside these categories." } ] }, { "type": "predicate", "name": "refund_requested", "instructions": "Does the customer ask for a refund or report a duplicate charge?" } ]}After (Mouse POST /v1/decisions)
Section titled “After (Mouse POST /v1/decisions)”{ "model": "mouse-1", "state": "I was charged twice for my order.", "questions": { "department": { "type": "choice", "instructions": "Which department should handle this complaint?", "criteria": { "billing": "Payments, invoices, and refunds.", "technical": "Problems using the product.", "shipping": "Delivery and tracking.", "other": "Requests outside these categories." } }, "refund_requested": { "type": "noul", "instructions": "Does the customer ask for a refund or report a duplicate charge?" } }}3. Migrating from generative LLM classifiers (chat/completions / responses)
Section titled “3. Migrating from generative LLM classifiers (chat/completions / responses)”If you currently prompt a chat model (claude-haiku-4-5, gpt-5.6, gemini-flash) with a JSON schema or forced tool call to classify intents, gate actions, or score outputs:
- Move your dynamic prompt variables into
stateas a clean JSON object (no prompt template boilerplate needed). - Turn each boolean schema field into a
noulquestion, each enum field into achoicequestion, and each 1–5 rating field into ascorequestion with concrete level descriptions. - Delete your JSON parser and retry-on-malformed-output loop:
mouse-1evaluates all questions in one forward pass (~40–100 ms) with$0output token cost and returns calibrated probabilities for every option.