API reference
Evaluate a state against a map of typed questions (choice, noul, score) in a single forward pass (~40–100 ms) and receive structured answers with calibrated probability distributions.
- Base URL:
https://api.mouse.dev(US-East) - Authentication:
Authorization: Bearer sk_mouse_...(create an API key) - Data retention: Zero data retention by default on every request (
Mouse-Data-Retention: none,Cache-Control: no-store) - OpenAPI 3.1 spec:
/openapi.json· Interactive Playground:/console/playground
| Method & endpoint | Auth | Description |
|---|---|---|
POST /v1/decisions | Bearer sk_mouse_... | Evaluate 1 to 128 typed choice, noul (yes/no), and score questions in parallel against a state (POST /v1/systemone is served as an identical alias). |
POST /v1/calibrations | Bearer sk_mouse_... | Fit a signed confidence cutoff profile (calp1.) on 200–5,000 labeled examples using 5-fold out-of-fold cross-fitting. |
GET /v1/models | Bearer sk_mouse_... | List immutable model versions (mouse-preview-2026-10-13) and current aliases (mouse-1, mouse). |
GET /healthz | None | Unauthenticated liveness and region probe. |
POST /v1/systemone
Section titled “POST /v1/systemone”Evaluate up to 128 typed questions in parallel and in isolation against a shared state.
POST https://api.mouse.dev/v1/systemoneAuthorization: Bearer <MOUSE_API_KEY>Content-Type: application/jsonRequest body
Section titled “Request body”| Property | Type | Required | Default | Description |
|---|---|---|---|---|
state | string | object | array | number | boolean | Required | — | The content to evaluate (up to 65,536 tokens). Pass a string for a single message or passage, or a JSON object/array for structured application state, tool proposals, and records. Never truncated; states over 65,536 tokens return 422 state_too_long. |
questions | Record<string, Question> | Required | — | Non-empty map of 1 to 128 question IDs you choose to typed ChoiceQuestion, NoulQuestion, or ScoreQuestion objects. Question IDs are returned as keys in answers and are not sent to the model during inference. |
model | string | Optional | "mouse-1" | Model alias ("mouse-1", "mouse") or immutable snapshot ID ("mouse-preview-2026-10-13"). |
calibration | string | null | Optional | null | Optional signed calibration profile token ("calp1.<kid>.<blob>") returned by POST /v1/calibrations. |
order_invariant | boolean | Optional | false | Score every multi-option choice question a second time with its options reversed and average the two distributions, removing option-order sensitivity. The reversed questions are billed again (their question_tokens and framing_tokens roughly double); the state prefix is shared and billed once. noul and score are never permuted. |
Unknown top-level fields (for example "temperature") are ignored and listed in the response's warnings array. Unknown fields inside a question object are refused with 422 invalid_schema.
Accepted aliases
Section titled “Accepted aliases”So that a migration is a base-URL change, the request parser also accepts, and normalizes before validation:
| You send | Read as |
|---|---|
input | state |
questions as an array of objects carrying id or name | questions map keyed by that id/name |
type: "predicate" or type: "boolean" | type: "noul" |
text | instructions |
options: ["a", "b"] | criteria: { "a": null, "b": null } |
choices: [{ value, description }] | criteria: { [value]: description } |
levels: [{ label, description }] | criteria: ["<label>: <description>", ...] |
Aliases change only how the request is read; the response is always the Mouse shape documented below.
Example request (cURL, TypeScript, Python)
Section titled “Example request (cURL, TypeScript, Python)”curl https://api.mouse.dev/v1/systemone \ -H "Authorization: Bearer $MOUSE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "mouse-1", "state": { "task": "Rename exported function `parseToken` to `verifyToken` in src/auth.ts.", "proposed_command": "pnpm --filter @mouse/relay test" }, "questions": { "next_tool": { "type": "choice", "instructions": "Which tool should handle `task` next?", "criteria": { "edit_file": "Modify source code in a file", "grep_search": "Search for symbols across the repository", "run_tests": "Execute the test suite", "ask_user": "Ask the user for clarification" } }, "safe_to_run": { "type": "noul", "instructions": "Is `proposed_command` safe to run without human approval?" }, "blast_radius": { "type": "score", "instructions": "What is the impact of `proposed_command`?", "criteria": [ "Read-only inspection or local test execution", "Reversible local workspace edit", "Irreversible deletion, remote git push, or external mutation" ] } } }'const res = await fetch("https://api.mouse.dev/v1/systemone", { method: "POST", headers: { Authorization: `Bearer ${process.env.MOUSE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "mouse-1", state: { task: "Rename exported function `parseToken` to `verifyToken` in src/auth.ts.", proposed_command: "pnpm --filter @mouse/relay test", }, questions: { next_tool: { type: "choice", instructions: "Which tool should handle `task` next?", criteria: { edit_file: "Modify source code in a file", grep_search: "Search for symbols across the repository", run_tests: "Execute the test suite", ask_user: "Ask the user for clarification", }, }, safe_to_run: { type: "noul", instructions: "Is `proposed_command` safe to run without human approval?", }, blast_radius: { type: "score", instructions: "What is the impact of `proposed_command`?", criteria: [ "Read-only inspection or local test execution", "Reversible local workspace edit", "Irreversible deletion, remote git push, or external mutation", ], }, }, }),});
const data = await res.json();console.log(data.answers.next_tool.choice, data.answers.safe_to_run.noul);import osimport requests
res = requests.post( "https://api.mouse.dev/v1/systemone", headers={"Authorization": f"Bearer {os.environ['MOUSE_API_KEY']}"}, json={ "model": "mouse-1", "state": { "task": "Rename exported function `parseToken` to `verifyToken` in src/auth.ts.", "proposed_command": "pnpm --filter @mouse/relay test", }, "questions": { "next_tool": { "type": "choice", "instructions": "Which tool should handle `task` next?", "criteria": { "edit_file": "Modify source code in a file", "grep_search": "Search for symbols across the repository", "run_tests": "Execute the test suite", "ask_user": "Ask the user for clarification", }, }, "safe_to_run": { "type": "noul", "instructions": "Is `proposed_command` safe to run without human approval?", }, "blast_radius": { "type": "score", "instructions": "What is the impact of `proposed_command`?", "criteria": [ "Read-only inspection or local test execution", "Reversible local workspace edit", "Irreversible deletion, remote git push, or external mutation", ], }, }, }, timeout=5.0,)print(res.json()["answers"])Question types
Section titled “Question types”A Question is one of three primitives determined by its type field ("choice", "noul", or "score").
All three primitives accept instructions as a string, a structured object, an array, or null. Point a question at a specific nested field inside state (or inside a structured instructions object) using backticks, such as "Does `ticket.messages[0].text` request a refund?".
choice question
Section titled “choice question”Picks one option from a set of 1 to 1,024 named options and returns the full probability distribution.
| Property | Type | Required | Description |
|---|---|---|---|
type | "choice" | Required | Always "choice". |
criteria | Record<string, string | object | array | null> | Required | Map of option keys to rubric descriptions. Use null when an option key needs no extra detail. Passing an array instead of an object returns 422 invalid_schema. |
instructions | string | object | array | null | Optional | What the model should decide. |
{ "department": { "type": "choice", "instructions": "Which team should handle this ticket?", "criteria": { "billing": "Payments, invoices, duplicate charges, or refunds", "technical": "Bugs, outages, or API integration problems", "sales": "Pricing, upgrades, or new accounts" } }}noul question
Section titled “noul question”Evaluates a yes/no predicate and returns the probability (0.0 to 1.0) that the statement is true (yes).
| Property | Type | Required | Description |
|---|---|---|---|
type | "noul" | Required | Always "noul". |
instructions | string | object | array | null | Optional | The yes/no question or statement to evaluate against state. Phrase positively so a value near 1.0 means yes. |
criteria | { "true"?: JSONContent, "false"?: JSONContent } | null | Optional | Optional descriptions clarifying what "true" (yes, near 1.0) and "false" (no, near 0.0) mean. Only "true" and "false" keys are allowed. |
{ "is_urgent": { "type": "noul", "instructions": "Does this message convey time-sensitive urgency?", "criteria": { "true": "Production outage, blocked customers, or explicit deadline", "false": "Routine question, feature request, or non-blocking issue" } }}score question
Section titled “score question”Rates the state along an ordered rubric of 2 to 10 concrete levels (0 to N - 1).
| Property | Type | Required | Description |
|---|---|---|---|
type | "score" | Required | Always "score". |
criteria | Array<string | object | array> | Required | Ordered array of 2 to 10 level descriptions from lowest (0) to highest (N - 1). Describe concrete situations rather than bare numbers. |
instructions | string | object | array | null | Optional | What dimension the model should rate. |
{ "frustration": { "type": "score", "instructions": "How frustrated does the customer appear?", "criteria": [ "Calm, stating facts", "Frustrated but civil", "Very angry or threatening to leave" ] }}Response body (POST /v1/systemone)
Section titled “Response body (POST /v1/systemone)”| Property | Type | Description |
|---|---|---|
id | string | Unique decision identifier ("dec_..."). |
object | "systemone.response" | Fixed object type string. |
model | string | Exact immutable model snapshot ID that evaluated the request ("mouse-preview-2026-10-13"). Also echoed in the Mouse-Model-Version HTTP header. |
answers | Record<string, Answer> | One typed ChoiceAnswer, NoulAnswer, or ScoreAnswer per question, keyed by the same IDs you passed in questions. |
usage | Usage | Itemized token counts and exact USD cost for the request. |
latency_ms | number | End-to-end server evaluation latency in milliseconds. |
warnings | string[] (optional) | Present only when unknown top-level request fields were ignored. |
Answer types
Section titled “Answer types”Every answer carries a type matching its question, the full probabilities distribution, the raw logits behind it (softmax(logits) == probabilities), and a mouse metadata object ({ calibrated, p_top, abstain, confidence_winning, confidence_normalized }). choice and score answers also carry a top-level confidence (0.0 to 1.0).
Log logits and model together if you fit thresholds or calibrators offline; probabilities alone lose the scale.
choice answer
Section titled “choice answer”| Property | Type | Description |
|---|---|---|
type | "choice" | Always "choice". |
choice | string | The highest-probability option key from criteria. |
probabilities | Record<string, number> | Every option key mapped to its probability (0.0 to 1.0, summing to 1.0). |
logits | Record<string, number> | Every option key mapped to its raw log-score. |
confidence | number | Normalized concentration score in 0.0 to 1.0: ((p_{\max} - 1/k) / (1 - 1/k)) for (k) options (0.0 for an even split, 1.0 when all probability is on one option). |
mouse | MouseAnswerMeta | { calibrated, p_top, abstain, confidence_winning, confidence_normalized }. |
{ "type": "choice", "choice": "edit_file", "confidence": 0.88, "probabilities": { "edit_file": 0.91, "grep_search": 0.06, "run_tests": 0.02, "ask_user": 0.01 }, "logits": { "edit_file": 2.41, "grep_search": -0.31, "run_tests": -1.41, "ask_user": -2.1 }, "mouse": { "calibrated": false, "p_top": 0.91, "abstain": null, "confidence_winning": 0.91, "confidence_normalized": 0.88 }}noul answer
Section titled “noul answer”| Property | Type | Description |
|---|---|---|
type | "noul" | Always "noul". |
noul | number | The yes/no probability on a scale from 0.0 (false / no) to 1.0 (true / yes). A value near 0.5 indicates equal probability for yes and no (certainty is (|2 \cdot \text{noul} - 1|), also returned as mouse.confidence_normalized). |
probabilities | { "false": number, "true": number } | The same distribution in the shape choice uses: { "false": 1 - noul, "true": noul }. |
logits | { "false": number, "true": number } | Raw log-scores for each side. |
mouse | MouseAnswerMeta | { calibrated, p_top, abstain, confidence_winning, confidence_normalized }, where p_top = max(noul, 1 - noul). |
{ "type": "noul", "noul": 0.98, "probabilities": { "false": 0.02, "true": 0.98 }, "logits": { "false": -1.95, "true": 1.94 }, "mouse": { "calibrated": false, "p_top": 0.98, "abstain": null, "confidence_winning": 0.98, "confidence_normalized": 0.96 }}score answer
Section titled “score answer”| Property | Type | Description |
|---|---|---|
type | "score" | Always "score". |
score | number | Probability-weighted mean level index: (\sum_{i=0}^{k-1} i \cdot p_i), landing continuously between 0.0 and k - 1. |
legend | Record<string, JSONContent> | Each string level index ("0", "1", …) mapped back to its description from criteria. |
probabilities | Record<string, number> | Each string level index ("0", "1", …) mapped to its probability (summing to 1.0). |
logits | Record<string, number> | Each string level index mapped to its raw log-score. |
confidence | number | Ordinal concentration score in 0.0 to 1.0: (\max(0, 1 - \sum_i p_i |i - m| / \text{MAD}_{\text{unif}})), where (m) is the most likely level. |
mouse | MouseAnswerMeta | { calibrated, p_top, abstain, confidence_winning, confidence_normalized }. |
{ "type": "score", "score": 0.04, "confidence": 0.94, "legend": { "0": "Read-only inspection or local test execution", "1": "Reversible local workspace edit", "2": "Irreversible deletion, remote git push, or external mutation" }, "probabilities": { "0": 0.96, "1": 0.04, "2": 0.0 }, "logits": { "0": 3.18, "1": 0.0, "2": -6.9 }, "mouse": { "calibrated": false, "p_top": 0.96, "abstain": null, "confidence_winning": 0.96, "confidence_normalized": 0.94 }}MouseAnswerMeta object
Section titled “MouseAnswerMeta object”| Property | Type | Description |
|---|---|---|
calibrated | boolean | true when a calibration profile was passed on the request and matched this question's signature. When true, probabilities (and every field derived from them) are temperature-scaled on your labels. |
p_top | number | Top outcome probability ((\max_i p_i)). |
abstain | boolean | null | null when uncalibrated; true when p_top is below the profile's fitted cutoff (signaling your code to escalate or fall back); false when p_top >= cutoff. |
confidence_winning | number | The winning-probability convention ((\max_i p_i)), as Cloudflare Clef reports confidence. Equal to p_top; named so a Clef threshold ports without arithmetic. |
confidence_normalized | number | The normalized convention ((p_{\max} - 1/k) / (1 - 1/k)), as TypeSafe reports confidence, on every answer type including noul. |
Both conventions are present because the same distribution reads differently under each: (0.80, 0.15, 0.05) is 0.80 winning and 0.70 normalized, so a 0.75 threshold flips depending on which one a vendor calls confidence. Threshold on the convention your labels were fitted against, or on the full probabilities.
Usage object
Section titled “Usage object”| Property | Type | Description |
|---|---|---|
input_tokens | integer | Total billed input tokens: state_tokens + question_tokens + framing_tokens + image_tokens. |
state_tokens | integer | Tokens in state (counted once per request regardless of how many questions are evaluated). |
question_tokens | integer | Tokens across all question instructions and criteria. |
framing_tokens | integer | Structural delimiter tokens: 4 per request + 3 per question + 2 per option (2 options per noul). |
image_tokens | integer | 0 for text/JSON requests. |
cached_tokens | integer | state_tokens served from the 5-minute in-memory prefix cache ($0.02 / 1M instead of $0.10 / 1M). |
output_tokens | integer | Always 0 (output is free). |
cost_usd | number | Exact USD cost for the request (((input_tokens - cached_tokens) * 0.10 + cached_tokens * 0.02) / 1_000_000). |
POST /v1/calibrations
Section titled “POST /v1/calibrations”Score 200 to 5,000 labeled examples and fit a signed confidence cutoff profile (calp1.<kid>.<blob>) using 5-fold out-of-fold cross-fitting. Examples are evaluated in memory and discarded immediately (Mouse-Data-Retention: none).
POST https://api.mouse.dev/v1/calibrationsAuthorization: Bearer <MOUSE_API_KEY>Content-Type: application/jsonRequest body
Section titled “Request body”| Property | Type | Required | Default | Description |
|---|---|---|---|---|
examples | CalExample[] | Required | — | Array of 200 to 5,000 labeled examples. Each distinct question signature requires 50+ labeled examples to be fitted. |
model | string | Optional | "mouse-1" | Model alias or snapshot ID. The returned profile is cryptographically bound to the resolved snapshot ID. |
target_error | number | Optional | 0.05 | Maximum acceptable error rate (0.0 < target_error < 0.5) among non-abstained predictions. |
Each item in examples contains state and questions, where each question includes its ground-truth label:
- Labeled
choice(CalChoice):labelis astringmatching one of the option keys incriteria. - Labeled
noul(CalNoul):labelis aboolean(trueorfalse). - Labeled
score(CalScore):labelis anintegerlevel index in0 .. len(criteria) - 1.
curl https://api.mouse.dev/v1/calibrations \ -H "Authorization: Bearer $MOUSE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "mouse-1", "target_error": 0.05, "examples": [ { "state": "Customer was charged twice for order #1042.", "questions": { "intent": { "type": "choice", "instructions": "What does the customer need?", "criteria": { "billing": "A charge, invoice, or refund issue", "shipping": "Delivery status or tracking", "account": "Login or profile settings" }, "label": "billing" } } } ] }'Response body (POST /v1/calibrations)
Section titled “Response body (POST /v1/calibrations)”| Property | Type | Description |
|---|---|---|
object | "calibration" | Fixed object type string. |
model | string | Immutable snapshot ID this profile was fitted against ("mouse-preview-2026-10-13"). |
target_error | number | Target error rate used to select the abstention cutoff. |
profile | string | HMAC-SHA256 signed profile token ("calp1.<kid>.<blob>"). Pass as calibration on POST /v1/systemone. |
n_examples | integer | Total labeled examples processed. |
questions | CalibrationQuestionReport[] | Per-question-signature report (signature, type, question_ids, status, before, and after metrics including the fitted temperature, cutoff, and 5-fold out-of-fold coverage_at_target_error and error_when_covered). |
note | string | Out-of-fold methodology and zero-data-retention note. |
usage | Usage | Itemized token counts and USD cost for scoring the calibration examples. |
{ "object": "calibration", "model": "mouse-preview-2026-10-13", "target_error": 0.05, "profile": "calp1.k1.eyJmaXRzIjpb...", "n_examples": 250, "questions": [ { "signature": "4f8c12a90e3b7104", "type": "choice", "question_ids": ["intent"], "status": "fitted", "before": { "accuracy": 0.912, "ece": 0.031, "brier": 0.142, "nll": 0.264, "mean_confidence": 0.938, "coverage": 1.0, "cutoff": null, "n": 250 }, "after": { "accuracy": 0.912, "ece": 0.031, "brier": 0.142, "nll": 0.264, "mean_confidence": 0.938, "coverage": 0.864, "cutoff": 0.78, "n": 250, "coverage_at_target_error": 0.856, "error_when_covered": 0.0421 } } ], "note": "'coverage_at_target_error' and 'error_when_covered' are 5-fold out-of-fold. Examples were not stored.", "usage": { "input_tokens": 14500, "state_tokens": 3500, "question_tokens": 7750, "framing_tokens": 3250, "image_tokens": 0, "cached_tokens": 0, "output_tokens": 0, "cost_usd": 0.00145 }}GET /v1/models
Section titled “GET /v1/models”List active immutable model versions and alias mappings (mouse-1, mouse).
curl https://api.mouse.dev/v1/models \ -H "Authorization: Bearer $MOUSE_API_KEY"{ "object": "list", "alias": { "mouse-1": "mouse-preview-2026-10-13", "mouse": "mouse-preview-2026-10-13" }, "data": [ { "id": "mouse-preview-2026-10-13", "released": "2026-10-13", "status": "preview", "base": "mouse-1", "max_state_tokens": 65536, "modalities": ["text"], "retire_after": null } ], "today": "2026-10-14"}GET /healthz
Section titled “GET /healthz”Unauthenticated liveness probe returning serving region and engine status.
curl https://api.mouse.dev/healthz{ "ok": true, "region": "us", "engine": "modal"}Response headers
Section titled “Response headers”Every response from https://api.mouse.dev includes these HTTP headers:
| Header | Example | Description |
|---|---|---|
Mouse-Request-Id | req_8f92a1c04e1b902d73c104ab | Unique request identifier (req_ + 24 hex chars). |
Mouse-Model-Version | mouse-preview-2026-10-13 | Exact immutable snapshot ID that scored the request (POST /v1/systemone and POST /v1/calibrations). |
Mouse-Region | us | Serving region (us). |
Mouse-Data-Retention | none | Enforced zero data retention on every response. |
Cache-Control | no-store | Prevents intermediary caching. |
x-ratelimit-limit-requests | 200 | Sustained requests-per-second limit (200 standard, 5 playground). |
x-ratelimit-remaining-requests | 399 | Remaining requests in the burst bucket (400 capacity). |
x-ratelimit-reset-requests | 1 | Seconds until the request bucket resets (1). |
x-ratelimit-limit-tokens | 20000000 | Input-tokens-per-minute limit (20,000,000). |
x-ratelimit-remaining-tokens | 19999926 | Remaining input tokens in the 60-second bucket. |
x-ratelimit-reset-tokens | 60 | Seconds until the token bucket resets (60). |
Retry-After | 2 | Present on 429 and 503 responses; integer seconds to wait before retrying. |
Errors
Section titled “Errors”All non-2xx responses use a single JSON error envelope. Messages name the limit or offending field and never echo request content:
{ "error": { "type": "invalid_request_error", "code": "state_too_long", "message": "state is 71,204 tokens; mouse-preview-2026-10-13 reads at most 65,536. Nothing was truncated or billed.", "param": "state" }}| Status | error.type | error.code | Meaning |
|---|---|---|---|
400 | invalid_request_error | invalid_calibration_profile | Malformed, altered, or retired calibration profile token. |
401 | authentication_error | missing_api_key, invalid_api_key | Missing Authorization: Bearer sk_mouse_... header, bad CRC32 checksum, or revoked/expired key. |
402 | billing_error | insufficient_credits | Prepaid credit balance is $0.00. Add credits at /console/billing. |
404 | invalid_request_error | model_not_found | Unknown model name. See GET /v1/models. |
409 | invalid_request_error | profile_model_mismatch | calibration profile was fitted on a different model snapshot than the one serving the request. |
413 | invalid_request_error | request_too_large | Request body exceeds 4,000,000 bytes (4 MB). |
422 | invalid_request_error | invalid_schema, state_too_long, too_many_questions, too_many_options, too_few_examples, too_many_examples, too_few_examples_per_question | Request failed schema validation or exceeded a published payload limit. Never billed. |
429 | rate_limit_error | rate_limited, token_rate_limited | Exceeded 200 RPS (burst 400) or 20M TPM. Honor the Retry-After header. |
503 | api_error | engine_timeout | Inference worker timed out (10s). Honor the Retry-After header. |