Jev on Cloudflare — builder's guide
Audience: an engineer building a substantial Cloudflare Workers project on typesafe/jev. Dense reference, not a tutorial. Citation prefixes: TS/ = /tmp/claude-1000/-home-stevan-dev/4bdbd082-4e99-49ae-a4be-bacdb74171a7/scratchpad/docs/typesafe/, CF/ = /tmp/claude-1000/-home-stevan-dev/4bdbd082-4e99-49ae-a4be-bacdb74171a7/scratchpad/docs/cloudflare/. Full paths in section 9. Where sources disagree, both are cited and the conflict is stated. "Community" = third-party repos and issues, not vendor docs.
Snapshot: 2026-09-24. Every documented Cloudflare sample answers as jev-1.13.0 [CF/ai__models__typesafe__jev.md].
1. What Jev is
- Jev is TypeSafe's "System One" model: it reads natural language like an LLM but never generates text; it returns typed decisions with calibrated probabilities [TS/concepts__system-one.md].
- Input is one
state(string, JSON object, or array; text only, English-first) plus a map of namedquestions[TS/concepts__state.md] [TS/models.md]. - Three question types: Noul (yes/no → P(yes) in 0..1), Choice (one of up to 255 options → per-option probabilities + confidence), Score (2–10 ordered levels → probability-weighted score + legend + probabilities + confidence) [TS/api.md] [TS/primitives.md].
- Every question in a request is evaluated in parallel and in isolation against the same state; no answer is hidden context for another [TS/primitives.md].
- Calibration is the promise: across many predictions, outcomes assigned 0.8 occur about 80% of the time. A population property, not a per-answer guarantee [TS/introduction__machine-learning-primer.md].
- Trained with RLCD ("reinforcement learning for calibrated decisions"), not RLHF; same weights for every account, no per-customer fine-tuning [TS/models.md] [TS/introduction__machine-learning-primer.md].
- Latency claim: about 100 ms per request on TypeSafe's endpoint [TS/concepts__how-to-build-with-system-one.md]. No Cloudflare-route latency figure exists in any source.
- Direct-API price: $0.042 per million input tokens, output free [TS/models.md]. Cloudflare's price is dashboard-only [CF/ai__models__typesafe__jev.md].
- Intended architecture: code owns control flow, rules and side effects; Jev answers atomic questions; you combine answers with weights and thresholds in code [TS/concepts__how-to-build-with-system-one.md].
- Not a drop-in LLM for coding agents and not a text generator; forcing generation by chaining Choices is documented as slow and poor [TS/introduction__coding-agents.md] [TS/model-jaggedness__jev-1.13.md].
2. Request/response contract
2.1 Input schema (Cloudflare input object)
From [CF/jev-schema-input.json]:
- Top level:
required: ["state","questions"],additionalProperties: false. Nomodelkey insideinput. state:string | object | array | null. A bare top-level number/boolean is invalid; numbers and booleans nested in an object are fine.questions: object; keys non-empty (minLength: 1); valuesoneOfthree shapes, eachadditionalProperties: false.instructions(all types):string | object | array | null; the key is required (nullable). TypeSafe's API doc marks it required [TS/api.md]; the JS SDK types it optional [TS/sdk__javascript__api__interfaces__ChoiceQuestion.md]. Always send it.- noul:
required: ["type","instructions"];criteriaoptional:nullor an object with onlytrue/falsekeys (additionalProperties: false), eachstring | object | array | null. - choice:
required: ["type","instructions","criteria"];criteriamapsoption_key -> string | object | array | null(null= no description). Max 255 options per TypeSafe [TS/api.md]; no maximum in the Cloudflare schema. - score:
required: ["type","instructions","criteria"];criteriais an array,minItems: 2, ordered low to high; index = level. TypeSafe accepts up to 10; 11 is a server error [TS/primitives__score.md] [TS/cookbooks__autoresearch_feature_discovery.md]; no maximum in the Cloudflare schema. - Question count: no maximum is documented on either side. The Cloudflare schema constrains only key non-emptiness (
minLength: 1, nomaxProperties) [CF/jev-schema-input.json]; TypeSafe constrains only tokens: 64k per request, 32k forstate+ longest question [TS/models.md]. Documented working sizes: 54 questions per request [TS/cookbooks__function_calling.md], 62 questions in one request [TS/cookbooks__autoformat.md], 182 options in one Choice [TS/cookbooks__skill_suggestion.md].
Structured instructions/criteria are how you embed data from code and reference paths with backticks: `ticket.messages[0].text` into state, or `potential_duplicate` into a sibling instruction field [TS/concepts__how-to-build-with-system-one.md] [TS/primitives__advanced.md]. Field names used in the docs (question, focus, compare, inspect, what, not_for, examples, signals) are conventions, not reserved keywords; the model sees them as labels [TS/primitives__choice.md] [TS/primitives__advanced.md]. Question ids are never sent to the model; put the whole question in instructions [TS/api.md] [TS/primitives.md].
2.2 Output schema
From [CF/jev-schema-output.json]:
- Top level:
required: ["model","answers","usage"],additionalProperties: false;modelis the versioned id that answered. - noul:
{ type: "noul", noul: 0..1 }. Noconfidencefield (schema forbids it). - choice:
{ type, choice: string, probabilities: {[option]: 0..1}, confidence: 0..1 }, all required.choiceis the argmax key; probabilities sum to 1 [TS/primitives__choice.md]. - score:
{ type, score: number (unbounded), legend: {[idx]: string}, probabilities: {[idx]: 0..1}, confidence: 0..1 }, all required. Keys are strings"0","1", ... The schema typeslegendvalues as strings, but TypeSafe shows structured criteria objects echoed back inlegend[TS/primitives__score.md]; schema-generated types are wrong for structured levels. usage:{ input_tokens, output_tokens }, non-negative integers.output_tokensis non-zero although Jev generates no text and output is free on the direct API [TS/models.md] [CF/ai__models__typesafe__jev.md].
2.3 Full request (Cloudflare REST)
[CF/ai__models__typesafe__jev.md]
curl https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/ai/run \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"model": "typesafe/jev",
"input": {
"state": "Help! My payouts have been failing for 3 days.",
"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"]
}
}
}
}'
2.4 Full response (evaluation object)
[CF/ai__models__typesafe__jev.md]
{
"model": "jev-1.13.0",
"answers": {
"is_urgent": { "type": "noul", "noul": 0.95 },
"department": {
"type": "choice",
"choice": "billing",
"confidence": 0.8,
"probabilities": { "billing": 0.87, "sales": 0, "technical": 0.13 }
},
"frustration": {
"type": "score",
"score": 1.04,
"confidence": 0.94,
"legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" },
"probabilities": { "0": 0, "1": 0.96, "2": 0.04 }
}
},
"usage": { "input_tokens": 426, "output_tokens": 73 }
}
Returned probabilities key order differs from request order; zero-probability options are included [CF/ai__models__typesafe__jev.md].
2.5 Native /v1/systemone vs Cloudflare /ai/run
| TypeSafe direct | Cloudflare | |
|---|---|---|
| Endpoint | POST https://api.typesafe.ai/v1/systemone [TS/api.md] |
POST https://api.cloudflare.com/client/v4/accounts/{id}/ai/run, or env.AI.run('typesafe/jev', {state, questions}) [CF/ai__models__typesafe__jev.md] |
| Auth | Bearer $TYPESAFE_API_KEY from console.typesafe.ai/keys [TS/introduction__quickstart.md] |
Bearer $CLOUDFLARE_API_TOKEN [CF/ai-gateway__usage__rest-api.md] |
| Body | {state, model, questions}, all required; model ∈ jev-latest, jev-preview, jev-1.13.0 [TS/api.md] [TS/models.md] |
{"model":"typesafe/jev","input":{state, questions}}; model inside input violates the schema [CF/jev-schema-input.json] |
| Version pinning | Pass the versioned id [TS/models.md] | None; only typesafe/jev is documented [CF/ai__models__typesafe__jev.md] |
| Response | Bare {model, answers, usage} [TS/api.md] |
Binding: bare per docs. REST: see 4.7 |
| Errors | 401, 422 (names the field), 429, 529 Overloaded [TS/api.md] | {success:false, errors:[{code,message}]} (community, 4.8) |
| Context | 64k/request; 32k for state + longest question [TS/models.md] | "32,000 tokens" [CF/ai__models__typesafe__jev.md]. Conflict; budget 32k |
| SDKs | @typesafe-ai/sdk v0.6.0 (Node 20+), typesafe-sdk 0.7.1 [TS/sdk__javascript.md] [TS/sdk__python__changelog.md] |
baseURL/base_url works only against endpoints that implement the TypeSafe OpenAPI spec; the Python docs show OpenRouter (~typesafe/jev-latest) and Vercel AI Gateway (typesafe-ai/jev) [TS/sdk__python__usage.md] [TS/sdk__python__changelog.md]. Cloudflare's /ai/run wraps the body in {model, input} while the SDK posts {state, model, questions} to /v1/systemone, so it does not fit; untested in any source [TS/sdk__javascript__api__interfaces__SystemOneRequestPayload.md] [CF/jev-schema-input.json] |
Hand-build the question JSON (identical to what the SDK helpers emit) and call env.AI.run directly. Hand-building is a Cloudflare-specific cost, not a Jev-wide one: if SDK typing, retry policy and request ids matter more than staying inside Cloudflare billing, Vercel AI Gateway and OpenRouter are the documented SDK-compatible alternatives [TS/sdk__python__usage.md]. Derived types (from the two schemas, not verbatim from any doc):
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
type Entry = string | { [k: string]: JsonValue } | JsonValue[] | null;
export type JevQuestion =
| { type: 'noul'; instructions: Entry; criteria?: { true?: Entry; false?: Entry } | null }
| { type: 'choice'; instructions: Entry; criteria: Record<string, Entry> } // native max 255
| { type: 'score'; instructions: Entry; criteria: [Entry, Entry, ...Entry[]] }; // 2..10 native
export interface JevInput { state: string | { [k: string]: JsonValue } | JsonValue[] | null; questions: Record<string, JevQuestion>; }
export type JevAnswer =
| { type: 'noul'; noul: number }
| { type: 'choice'; choice: string; probabilities: Record<string, number>; confidence: number }
| { type: 'score'; score: number; legend: Record<string, Entry>; probabilities: Record<string, number>; confidence: number };
// legend: the Cloudflare output schema types values as `string`, narrower than observed behaviour; structured levels
// (objects such as {what, examples}) are echoed back as objects [TS/primitives__score.md] [CF/jev-schema-output.json].
export interface JevOutput { model: string; answers: Record<string, JevAnswer>; usage: { input_tokens: number; output_tokens: number } }
Overflow is undocumented on both sides: no tokenizer is published, no error code is specified for an over-budget state, and truncation is not described. TypeSafe's generic 422 "failed validation" is the closest documented error, and how Cloudflare relays it is unknown [TS/models.md] [TS/model-jaggedness__jev-1.13.md] [CF/ai__models__typesafe__jev.md] [TS/api.md] [CF/jev-schema-input.json]. Estimate tokens conservatively (≈ 4 chars/token), keep state + longest question well under 32k, and chunk long documents in code (one request per chunk, or the Noul relevance filter from the RAG cookbook [TS/cookbooks__classifying_rag_passages.md]). Treat any 4xx from an oversized request as a hard failure, not a retry.
3. Semantics
3.1 Probability vs confidence, per type
- Noul:
noulis P(yes). 0.5 means yes and no are equally weighted, not "a medium amount of the thing" [TS/primitives.md]. No confidence field; the value is the certainty [TS/confidence.md]. Recorded jev-1.13.0 values for "Is the customer asking for a human agent?": "Thanks, that fixed it!" 0.02; "Are you a bot?" 0.40; "Can I please just talk to a real person?" 0.99 [TS/primitives__noul.md]. Several nouls returned exactly 0.990 or 0.010 on every repeat (std 0.0), which suggests clipping near the extremes; the cookbook does not characterise this [TS/cookbooks__parallel_questions.md]. - Choice:
probabilitiessums to 1;choiceis its argmax.confidenceis a separate shape statistic (all mass on one option = 1.0; evenly spread = low). It is not the top probability: 0.87 top → 0.80 confidence [CF/ai__models__typesafe__jev.md]; 0.53 → 0.43 [TS/cookbooks__autoformat.md]. A Choice is relative: it always crowns a winner even when nothing applies [TS/model-jaggedness__jev-1.13.md] [TS/cookbooks__semantic_find.md]. - Score:
score= Σ(level_index × probability), a fractional expected value (1.04, 1.84; 3.0 on a 4-level rubric) that can land between levels [TS/primitives__score.md] [CF/ai__models__typesafe__jev.md]. Different distributions give the same score (all on 1, or half on 0 and half on 2 → both 1.0); readconfidence/probabilitiesalongside it. Confidence 1.0 means all mass on one level, not correctness [TS/primitives__score.md].
3.2 The confidence formula
Unpublished. The docs' demo approximates it as (n × max_probability − 1) / (n − 1) clamped to [0,1] and promises a cookbook [TS/confidence.md]. It reproduces every published Choice example and every 3-level Score example to within rounding (0.87/3 → 0.805 ≈ 0.8; 0.96/3 → 0.94; 0.85/3 → 0.775 ≈ 0.78) [CF/ai__models__typesafe__jev.md] [TS/introduction__quickstart.md], but not the 4-level Score example (max 0.52 → reported 0.52, formula 0.36) or the 5-level one (max 0.86 → 0.89, formula 0.83) [TS/primitives__score.md]. Do not depend on it; if you need a defined statistic, compute your own from probabilities, which the docs explicitly permit [TS/confidence.md].
3.3 Calibration and repeatability
Across groups of predictions, a 0.2 fires ~20% of the time and a 0.95 ~95% [TS/introduction__machine-learning-primer.md]; a single 0.95 can still be wrong. Repeatability is high but not deterministic: identical-except-uid calls gave per-question std ≈ 0.01, yet one Noul ranged 0.43–0.53 across 15 calls and two of eight Choice argmaxes flipped [TS/cookbooks__consistency_noul_cookbook.md] [TS/cookbooks__consistency_choice_cookbook.md]. Design bands, not knife-edges.
No structural invariants hold across questions: Noul "refund" + Noul "not refund" summed to 1.19; Noul refund = 0.22 while the same ticket as a yes/no Choice gave no = 0.99 at confidence 0.97. Thresholds do not transfer between types [TS/model-jaggedness__jev-1.13.md].
3.4 How to threshold
- Noul: 0.5 when yes and no cost the same; raise it when a false yes is expensive, lower when a missed yes is; send a middle band (0.2–0.8, or the inclusive 0.30–0.70 used in the consistency cookbook) to a person [TS/primitives__noul.md] [TS/cookbooks__consistency_noul_cookbook.md].
- Choice/Score: three bands on
confidence— high: act; medium: confirm/flag; low: do not act [TS/confidence.md]. The docs' banking example uses a 0.5 floor and > 0.9 for a destructive action on one page [TS/confidence.md] and 0.6 / > 0.85 on another [TS/patterns__confidence-routing.md]. Illustrative and mutually inconsistent; start conservative and plot confidence vs accuracy on your data [TS/confidence.md]. - Alternatively gate on
max(probabilities)when you have a specific statistical rule (the consistency-choice cookbook uses a 0.60 floor) [TS/agent-skill.md] [TS/cookbooks__consistency_choice_cookbook.md]. Pick one; be explicit. - If you only need the best option, take the argmax with no threshold [TS/agent-skill.md].
- Score without tuned thresholds: round to nearest level,
min(int(score + 0.5), levels − 1)[TS/cookbooks__entity_alignment.md]. Normalise bylen(criteria) − 1, notlen(criteria)[TS/primitives__score.md] [TS/patterns__composite-scoring.md].
3.5 Legend, level ordering, option ordering
Levels are zero-indexed by array position: a 3-item rubric scores in [0, 2]. The model sees only level descriptions, never numbers or neighbours; "worse than the previous level" means nothing, and numbers-only criteria ['0','1','2'] split probability (0.55, confidence 0.33) where descriptive levels gave 0.0 at 1.0 [TS/primitives__score.md]. One dimension per Score; give rare extremes their own level [TS/primitives__score.md]. Do not interpolate a score to recover a magnitude between levels; thresholding is fine, reconstruction is not [TS/model-jaggedness__jev-1.13.md]. Choice option order is part of the question [TS/cookbooks__hierarchical_classification.md].
4. Cloudflare integration
4.1 Catalog facts
Model id typesafe/jev, labelled "Text Generation" / "Third-party", context window 32,000 tokens, terms link to https://docs.typesafe.ai/legal.md, pricing "View pricing in the Cloudflare dashboard" (https://dash.cloudflare.com/?to=/:account/ai/models/typesafe/jev) [CF/ai__models__typesafe__jev.md]. "Text Generation" is a catalog category only; there is no response or streaming field.
4.2 Wrangler binding
[CF/workers-ai__configuration__bindings.md] [CF/workers-ai__get-started__workers-wrangler.md]
// wrangler.jsonc
{ "ai": { "binding": "AI" } }
# wrangler.toml
[ai]
binding = "AI"
export interface Env { AI: Ai }
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const r = (await env.AI.run('typesafe/jev', {
state: { ticket: { message: 'I was charged twice for order A-104.' } },
questions: {
refund_requested: { type: 'noul', instructions: 'Does `ticket.message` request a refund?' },
},
})) as JevOutput;
return Response.json(r);
},
} satisfies ExportedHandler<Env>;
Scaffold: npm create cloudflare@latest -- hello-ai (Hello World, Worker only, TypeScript); Wrangler needs Node ≥ 16.17.0. npx wrangler dev always accesses your Cloudflare account to run AI models and incurs usage charges even in local development [CF/workers-ai__get-started__workers-wrangler.md]. Pages Functions cannot declare the AI binding in wrangler config; use the dashboard [CF/workers-ai__configuration__bindings.md]. No source says whether @cloudflare/workers-types types typesafe/jev; cast to the types in 2.5.
4.3 Gateway option
Third-party requests route through the auto-created default gateway with no header; pass cf-aig-gateway-id: <id> (REST) or { gateway: { id } } as the third env.AI.run argument to choose a gateway, whose caching, rate limiting, guardrails and logging then apply [CF/ai-gateway__usage__rest-api.md] [CF/ai-gateway__features__unified-billing.md]. Per-request headers: cf-aig-skip-cache, cf-aig-cache-ttl, cf-aig-cache-key, cf-aig-collect-log, cf-aig-request-timeout (ms), cf-aig-max-attempts (≤ 5), cf-aig-retry-delay (ms, ≤ 60000), cf-aig-backoff (constant|linear|exponential), cf-aig-metadata (JSON) [CF/ai-gateway__usage__rest-api.md]. The Unified Billing page lists six provider-native endpoints (OpenAI, Anthropic, Google AI Studio, Google Vertex AI, xAI, Groq) and the get-started page says the provider list is longer ("and more...") [CF/ai-gateway__features__unified-billing.md] [CF/ai-gateway__get-started.md]. No local source documents a TypeSafe provider-native path; env.AI.run and /ai/run are the only documented routes for typesafe/jev. Community measurement: path-style POST /ai/run/typesafe/jev returns 400 "No route for that URI"; only the body form works [https://github.com/yottayoshida/jev-intent-review].
- Logging: AI Gateway logging is a gateway feature and applies to Jev traffic through
default("All AI Gateway features configured on that gateway — caching, rate limiting, guardrails, and logging — apply to the request") [CF/ai-gateway__usage__rest-api.md] [CF/ai-gateway__get-started.md]. Request and response bodies (yourstateandquestions, the answers) are stored by Cloudflare under the gateway's logging settings regardless of ZDR: "ZDR does not control AI Gateway logging. To disable request/response logging in AI Gateway, update the logging settings separately" [CF/ai-gateway__features__unified-billing.md]. Before sending customer data: AI Gateway → your gateway → Logging and disable request/response logging, or sendcf-aig-collect-log: falseper REST request. Whether the binding exposes an equivalent per-call switch is not in the captured sources (binding-reference gap below). - Caching: applies to Jev if enabled on the gateway you route through. For evaluation/consistency runs send
cf-aig-skip-cache: true, or add a per-runuidfield tostateas the consistency cookbooks do ("Every query also gets a freshuid, a throwaway unique value that changes each run") [CF/ai-gateway__usage__rest-api.md] [TS/cookbooks__consistency_noul_cookbook.md]. For production, caching identical evaluations is a valid cost lever (cf-aig-cache-ttl), but a cached response carries themodelid that answered at cache time, so version drift is hidden until the TTL expires. - Correlation: over REST attach
cf-aig-metadata: {"ticket":"…","user":"…"}to every call; it lands on the AI Gateway log entry and can scope spend-limit rules by user/team [CF/ai-gateway__usage__rest-api.md] [CF/ai-gateway__features__unified-billing.md]. Log the runidfrom the REST envelope (4.7). TypeSafe'sx-typesafe-request-id(SDKrequestId,error.request_id) is documented only for the direct API; do not expect it through Cloudflare [TS/sdk__javascript__api__interfaces__WithResponse.md] [TS/sdk__python__usage.md]. Through the binding, per-call metadata is unverified (binding reference not captured). - Binding reference gap: the captured sources show only
gateway: { id }on the binding; Cloudflare points tohttps://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/for the full third-argument surface (cache, logging, metadata, timeout) [CF/ai-gateway__features__unified-billing.md] [CF/ai-gateway__usage__rest-api.md]. That page was not captured; fetch it before choosing REST over the binding for any per-request control.
4.4 REST endpoint
POST https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run with Authorization: Bearer <token> and the {model, input} envelope [CF/ai-gateway__usage__rest-api.md]. The /ai/v1/* chat-shaped endpoints do not fit Jev. Background mode (options: { background: true, webhookUrl }) exists but is intended "for long-running models — such as image, video, or audio generation". Do not use it for Jev: the call takes ~100 ms [TS/concepts__how-to-build-with-system-one.md], "webhook delivery is best-effort and is not retried", and the destination must be an HTTPS URL that does not resolve to a private network address; webhookUrl without background: true → 400 [CF/ai-gateway__usage__rest-api.md]. Call synchronously. Account id: wrangler whoami [CF/ai-gateway__usage__rest-api.md] or the Workers AI "Use REST API" panel [CF/workers-ai__get-started__rest-api.md]; it is 32 hex chars, which community clients validate before interpolating [https://github.com/clouatre-labs/decisions-judge-mcp].
4.5 Token: exact steps and permissions
You create it yourself; nothing can mint it for you.
Choose the token kind first. The Workers AI template and the Custom flow under My Profile → API Tokens create user tokens bound to you (the template token "will be visible on your profile"); for a service integration create an Account API token (Manage Account → API Tokens) so it is not tied to a person, after checking the compatibility matrix for the /ai/* endpoints [CF/fundamentals__api__get-started__create-token.md] [CF/workers-ai__get-started__rest-api.md]. A production Worker/REST integration on a personal token breaks when that person leaves. /user/tokens/verify (below) is the user-token verify path; account tokens have their own (not in the captured sources).
- Template: Workers AI page → Use REST API → Create a Workers AI API Token → Create → copy. The same panel shows the Account ID [CF/workers-ai__get-started__rest-api.md].
- Custom:
https://dash.cloudflare.com/profile/api-tokens/→ Create Token → Custom → name → permissions → select the resources (the account) the token may access → optional IP filter and TTL → Continue to summary → Create Token → copy. Shown once;cfut_prefix [CF/fundamentals__api__get-started__create-token.md]. - Local:
npx wrangler loginthennpx wrangler auth token. Cloudflare's own/ai/runexamples usewrangler auth tokenas the bearer token, so it is documented as working for this endpoint; no page lists which permission scopes it carries [CF/ai-gateway__usage__rest-api.md] [CF/ai-gateway__get-started.md].
Verify: GET https://api.cloudflare.com/client/v4/user/tokens/verify → status: "active", message code 10000 "This API Token is valid and active" [CF/fundamentals__api__get-started__create-token.md]. Code 10000 is also the 401 error code on /ai/* for a token lacking Workers AI permission [CF/ai-gateway__usage__rest-api.md]; do not branch on the code alone.
Permissions — sources conflict: Account > Workers AI > Read suffices for all /ai/* endpoints; AI-Gateway-only tokens get 401 code 10000 [CF/ai-gateway__usage__rest-api.md]. A custom token needs Workers AI Read and Edit [CF/workers-ai__get-started__rest-api.md]. Recommended set: AI Gateway Read + Edit, Workers AI Read [CF/ai-gateway__get-started.md]. Community live-verified: Workers AI Edit alone works; 403 = missing permission [https://github.com/clouatre-labs/decisions-judge-mcp/issues/40]. Safe choice: Workers AI Read + Edit plus AI Gateway Read + Edit.
4.6 Unified billing: credits, fee, top-up, limits, BYOK
- Third-party models are metered from the AI Gateway prepaid credit balance, not a Workers Paid plan: "Third-party models are billed via Unified Billing. Workers AI models can use prepaid AI Gateway credits or Workers AI billing"; "ensure your Cloudflare account has sufficient credits loaded before calling third-party models" [CF/ai-gateway__usage__rest-api.md]. Community live-verified: a valid card with zero credits → HTTP 402, code 2021, "Insufficient balance; add money to your gateway or use BYOK", regardless of gateway billing setting or whether a gateway was named [https://github.com/clouatre-labs/decisions-judge-mcp/issues/40] [https://github.com/dxos/dxos/pull/13389].
- 5% fee on all credit purchases ($100 → $105); provider pricing passed through with no markup; balance can go negative and is then charged to the card monthly [CF/ai-gateway__features__unified-billing.md].
- Top-up: AI Gateway page → Credits Available → Manage → (add payment method) → Top-up credits → Confirm. Auto top-up: Manage → Setup auto top-up → threshold + amount. Spend limits: per-gateway rules by model, provider, or metadata [CF/ai-gateway__features__unified-billing.md].
- Credential precedence: provider key on request → BYOK key under alias
default→ Unified Billing. Onenv.AI.run()and/ai/v1/*only thedefaultalias counts; others silently fall through to credits. "Require provider credentials" (byok_only: true) returns 400 instead of spending credits. Per-request guard:cf-aig-no-wholesale: truemakes one third-party request fail with 400 rather than fall through to Unified Billing credits; it can tighten but never loosen the gateway setting [CF/ai-gateway__features__unified-billing.md]. Only useful once TypeSafe is confirmed as a BYOK provider slug. - BYOK: keys live in Secrets Store, named exactly
{gateway_id}_{provider_slug}_{alias}when created via API; a placeholderAuthorizationheader is forwarded and breaks provider auth [CF/ai-gateway__configuration__bring-your-own-keys.md]. Whether TypeSafe is a BYOK provider slug is undocumented; the 402 text implies it, nothing confirms it. - ZDR: routes Cloudflare-managed-credential traffic to non-retaining endpoints; does not cover BYOK or AI Gateway's own logging; check per model. The Jev catalog page shows no ZDR marker [CF/ai-gateway__features__unified-billing.md] [CF/ai__models__typesafe__jev.md].
4.7 Response envelope (REST vs binding)
The Jev page shows a bare {model, answers, usage} after both examples [CF/ai__models__typesafe__jev.md]; the generic Workers AI REST doc shows {result, success, errors, messages} [CF/workers-ai__get-started__rest-api.md]. Community live capture of /ai/run (2026-09-21):
{
"result": {
"state": "Completed",
"result": { "model": "jev-1.13.0", "answers": { "q1": { "type": "noul", "noul": 0.99 } },
"usage": { "input_tokens": 292, "output_tokens": 21 } },
"gatewayMetadata": { "keySource": "Unified" }
},
"success": true, "errors": [], "messages": []
}
[https://github.com/clouatre-labs/decisions-judge-mcp/issues/40], independently matched by [https://github.com/yottayoshida/jev-intent-review]. Over REST the evaluation is at result.result, two levels down, not the bare sample the Jev page shows. This matches the run object Cloudflare documents for background webhooks — {id, state, result, error, provider, model, usage} — placed inside the standard v4 {result, success, errors, messages} envelope [CF/ai-gateway__usage__rest-api.md] [CF/workers-ai__get-started__rest-api.md]; the shape is vendor-supported, not merely community-observed. So for sync REST read the evaluation at body.result.result, check body.result.state === "Completed" and body.result.error, and log body.result.id as the Cloudflare-side request id. Expect a run-level usage as well as result.result.usage; treat the inner one as the TypeSafe figure. An unmerged community PR reads json.result.answers and was never tested against a real success [https://github.com/gargpratyush/jev-router]. No captured binding response has been published. Unwrap defensively: walk .result until an object with answers appears.
4.8 Errors you will see
- 402, code 2021 "Insufficient balance; add money to your gateway or use BYOK" — no credits (community, 4.6).
- 400 "No route for that URI" — path-style URL; use the body form (community, 4.3).
- 401, code 10000 — token lacks Workers AI permission [CF/ai-gateway__usage__rest-api.md]; 403 — missing permission (community).
- 400 —
webhookUrlwithoutbackground,webhookFormatwithoutwebhookUrl[CF/ai-gateway__usage__rest-api.md], or abyok_onlygateway with no credentials [CF/ai-gateway__features__unified-billing.md]. - 429 — Workers AI free-allocation exhaustion reads "you have used up your daily free allocation of 10,000 neurons" (observed on a
@cf/model, unconfirmed for Jev) [https://github.com/yottayoshida/jev-intent-review]. - Upstream TypeSafe: 401, 422 (body names the field), 429, 529 Overloaded [TS/api.md]. How Cloudflare relays 422/529 is undocumented. Community clients retry {429, 500, 502, 503, 504, 529} with exponential backoff, honour
retry-aftercapped at 30 s, fail fast on 401/402/403 [https://github.com/yottayoshida/jev-intent-review].
The binding has none of the SDK's retry policy. Mirror the SDK defaults in your Worker: retry on 408, 429 and 500–599 (529 included), max 2 retries, backoff 500 ms doubling to 5 s with 25% jitter, honour Retry-After/retry-after-ms up to 60 s; fail fast on 400/401/402/403/422 [TS/sdk__javascript__api__interfaces__RetryPolicy.md] [TS/sdk__python__api__retries.md]. There is no idempotency key in either API and none is needed: evaluation has no side effects, so a duplicate attempt only costs another ≈ 300+ input tokens, which is what is charged [TS/api.md] [TS/models.md]. Do not retry 422 (bad question shape); fix the request. On REST the gateway can do this for you with cf-aig-max-attempts (≤ 5), cf-aig-backoff: exponential, cf-aig-retry-delay [CF/ai-gateway__usage__rest-api.md].
Timeouts: the vendor SDKs default to 10 s per attempt (JS timeout: 10000, "without a total retry budget"; Python DEFAULT_TIMEOUT = 10.0) and Python caps the whole retry sequence at 30 s (RetryPolicy.timeout = 30.0) [TS/sdk__javascript__api__interfaces__TypeSafeClientConfig.md] [TS/sdk__python__api__constants.md] [TS/sdk__python__api__retries.md]. Over REST set cf-aig-request-timeout: 10000 [CF/ai-gateway__usage__rest-api.md]. env.AI.run exposes no timeout in any captured source; race it against your own deadline (Promise.race with a timer/AbortController) and treat a timeout as "unknown, do not act", the same branch as low confidence. Typical latency is ~100 ms [TS/concepts__how-to-build-with-system-one.md], so a 10 s budget is generous, not tight.
4.9 Rate limits
Undocumented for typesafe/jev on Cloudflare. TypeSafe direct: 250,000 tokens/s and 1,200 rpm, "adjusting dynamically" [TS/models.md]. Cookbook thread pools range from 4 to 16 workers: entity alignment 6, with a code comment that "the public endpoint rate-limits above roughly eight" [TS/cookbooks__entity_alignment.md]; RAG classification 4 [TS/cookbooks__classifying_rag_passages.md]; autoresearch 8 [TS/cookbooks__autoresearch_feature_discovery.md]; skill suggestion 8 [TS/cookbooks__skill_suggestion.md]; rerank 12 [TS/cookbooks__rerank_typesafe.md]; both consistency cookbooks 16 [TS/cookbooks__consistency_noul_cookbook.md] [TS/cookbooks__consistency_choice_cookbook.md]. Cloudflare's 50 rpm (prepaid) vs 20 rpm (standard) figures apply to frontier Workers AI models only [CF/changelog__post__2026-08-07-workers-ai-unified-billing.md]. No source gives a concurrency limit for the Cloudflare route; measure it.
4.10 Pricing
TypeSafe direct: $42 per billion = $0.042 per million input tokens, output free [TS/models.md]. Cloudflare: dashboard only [CF/ai__models__typesafe__jev.md]; Unified Billing passes provider pricing through with no markup [CF/ai-gateway__features__unified-billing.md], so $0.042/Mtok is the expected figure, but nothing confirms it or whether output_tokens are billed. The API reference's one-question examples on a one-sentence state report 296–318 input tokens, so budget roughly 300 tokens before your own state and questions (guide's inference from examples; no source states a fixed overhead) [TS/api.md]; short state plus 1–3 questions ran 380–430 tokens in the Cloudflare samples [CF/ai__models__typesafe__jev.md].
Reconcile on day one: note the credit balance (AI Gateway → Credits Available) [CF/ai-gateway__features__unified-billing.md], run a known batch (e.g. 100 calls) logging usage.input_tokens and usage.output_tokens (the only usage fields the output schema gives; no cost field) [CF/jev-schema-output.json], then read the balance delta and the gateway analytics cost figure ("token usage, and costs") [CF/changelog__post__2026-08-07-workers-ai-unified-billing.md]. That yields the effective $/Mtok and whether output tokens are charged. Set a per-gateway spend-limit rule before load-testing, and use cf-aig-metadata dimensions if you need per-tenant caps [CF/ai-gateway__features__unified-billing.md]. Keep usage per decision in your own store; the dashboard is the only documented price source [CF/ai__models__typesafe__jev.md].
4.11 Terms and data handling
The Cloudflare page links TypeSafe's legal index: DPA, Master Customer Agreement, Privacy Policy; ZDR for enterprise via privacy@typesafe.ai [TS/legal.md]. Jev is not trained on customer requests [TS/models.md]. No click-through acceptance is documented on Cloudflare, and no source says whether TypeSafe's DPA/no-training/ZDR commitments cover traffic proxied through Workers AI. Unresolved; do not send private data until it is. Separately from that question, AI Gateway logging applies to every Jev request through default and stores request and response bodies regardless of ZDR; disable request/response logging on the gateway or send cf-aig-collect-log: false before sending customer data (4.3) [CF/ai-gateway__usage__rest-api.md] [CF/ai-gateway__features__unified-billing.md].
5. Design patterns
All pattern code in the docs is Python; answer shapes are identical on Cloudflare, so port 1:1 with r.answers.x.choice and string keys for score probabilities [TS/patterns.md] [CF/ai__models__typesafe__jev.md].
5.1 Speculative fan-out
When: several judgments, some only relevant on certain branches. Put all in one request; extra questions barely change latency, and a second request is justified only when you cannot build it without the first answer [TS/patterns__fan-out.md] [TS/primitives.md]. Measured: 13 questions batched = 12.2× cheaper, 10.0× faster, answers unchanged [TS/cookbooks__parallel_questions.md]; primitives.md quotes the same cookbook as 11.5× / 9.6× [TS/primitives.md].
const r = await env.AI.run('typesafe/jev', { state: ticketText, questions: {
category: { type: 'choice', instructions: 'Determine the broad category of this support ticket',
criteria: { bug_report: 'Broken or producing errors', billing: 'Charges, invoices, refunds, subscriptions',
feature_request: 'Requesting new functionality', account: 'Login, permissions, profile, security' } },
bug_severity: { type: 'score', instructions: 'How severe is the reported issue',
criteria: ['Cosmetic; no impact to functionality', 'Broken or degraded feature; workaround exists', 'Blocking issue; no workaround exists'] },
has_reproducible_steps: { type: 'noul', instructions: 'The user describes specific steps to reproduce the issue' },
refund_requested: { type: 'noul', instructions: 'The user is explicitly asking for a refund or credit' },
frustration: { type: 'score', instructions: 'How frustrated the user appears', criteria: ['Calm, matter-of-fact', 'Frustrated but civil', 'Very angry'] },
}}) as JevOutput;
const a = r.answers as any;
if (a.category.choice === 'bug_report') {
if (a.bug_severity.score > 1.5 && a.has_reproducible_steps.noul > 0.6) escalate(ticketId, 'high'); else backlog(ticketId);
} else if (a.category.choice === 'billing') routeBilling(ticketId, { refundLikely: a.refund_requested.noul > 0.7 });
if (a.frustration.score > 1.5) flagPriority(ticketId);
Thresholds from [TS/patterns__fan-out.md]; port derived from that page plus the Cloudflare response shape.
5.2 Confidence-gated routing
When: the cost of a wrong action varies by branch. "The answer tells you what; confidence tells you whether to act" [TS/patterns__confidence-routing.md].
const intent = a.intent; // choice over check_balance | approve_transfer | other
if (intent.confidence < 0.6) return toHuman();
if (intent.choice === 'check_balance') return showBalance(); // low stakes
if (intent.choice === 'approve_transfer')
return intent.confidence > 0.85 ? approve() : askToConfirm(); // high stakes
return toHuman();
Numbers from [TS/patterns__confidence-routing.md]; the confidence page uses 0.5 / 0.9 for the same example [TS/confidence.md]. Tune on your data.
5.3 Composite scoring
When: one judgment hides several dimensions. Score each separately, normalise by levels − 1, weight in code; reuse the same answers under different weight sets [TS/patterns__composite-scoring.md] [TS/primitives__score.md].
const norm = (q: string) => (a[q].score as number) / (QUESTIONS[q].criteria.length - 1);
const py = norm('python_depth'), lead = norm('team_leadership'), arch = norm('system_design'), gen = norm('generalist');
const seniorIC = 0.40 * py + 0.10 * lead + 0.40 * arch + 0.10 * gen;
const engManager = 0.15 * py + 0.40 * lead + 0.20 * arch + 0.25 * gen;
Weights verbatim from [TS/patterns__composite-scoring.md]. Nouls compose the same way: spam_risk = 0.45·requests_credentials + 0.30·sender_identity_mismatch + 0.25·unexpected_reward, 0.4–0.6 to review [TS/concepts__how-to-build-with-system-one.md].
5.4 Intent routing
When: a cheap classifier in front of expensive handlers (deterministic code, specialist LLM, human) [TS/patterns__intent-routing.md].
const { intent, complexity } = a; // choice: order_status|product_question|return_exchange|complaint ; score: 3 levels
if (intent.confidence < 0.5) return human(ticketId);
switch (intent.choice) {
case 'order_status': return lookupOrder(ticketId); // no LLM
case 'product_question': return llm(ticketId, PRODUCT_SPECIALIST);
case 'return_exchange': return llm(ticketId, RETURNS_SPECIALIST);
case 'complaint': return (complexity.score > 1 || complexity.confidence < 0.5) ? human(ticketId) : llm(ticketId, COMPLAINT_RESOLUTION);
}
The docs' intent Choice has no other option although the primitives page says to add one when coverage is uncertain; the confidence floor does that job [TS/patterns__intent-routing.md] [TS/primitives.md]. Pair a relative Choice with an absolute Noul ("does any option apply?") when out-of-scope input is possible [TS/cookbooks__semantic_find.md].
5.5 Supporting idioms
- Code-generated questions: one Noul per candidate, data in a structured
instructionsfield, same question text (same_as_record_18: { instructions: { potential_duplicate: {...}, question: 'Is the resume for the same person aspotential_duplicate?' } }) [TS/primitives__noul.md]. Also how to count: one Noul per item, sumnoul > thresholdin code [TS/model-jaggedness__jev-1.13.md]. - Choice over ids or spans: options are line ids or regex-found spans with
nulldescriptions;choiceis a verbatim copy, so Jev cannot invent a value [TS/cookbooks__semantic_find.md] [TS/cookbooks__pre_parsed_value_extraction_cookbook.md]. The cookbook searches up to 255 lines in one Choice; past 255 it searches in two passes (one Choice picks a window of lines, a second ranks the lines inside it) [TS/cookbooks__semantic_find.md].
6. Cookbook index
All Python against api.typesafe.ai, mostly pinned to jev-1.12; the two consistency cookbooks ran on jev-1.13.0 [TS/cookbooks__parallel_questions.md] [TS/cookbooks__consistency_noul_cookbook.md]. Titles from [TS/cookbooks.md].
- Self-consistency: nouls — 15 identical-except-uid calls, mean probability std 0.0102;
coveredranged 0.43–0.53 across a 0.5 threshold; an inclusive 0.30–0.70 band returnsuncertaininstead of forcing yes/no (no agreement percentages are reported); 14-Noul call ≈ 111 ms, $0.000043 [TS/cookbooks__consistency_noul_cookbook.md]. - Self-consistency: choices — 8 Choices × 15 runs, std 0.0098, two argmaxes flipped;
max(probabilities) >= 0.60else "uncertain", which lifted policy agreement 90.8% → 99.2% with 25.8% uncertain and 74.2% automatic [TS/cookbooks__consistency_choice_cookbook.md]. - Parallel questions — 13 questions over the GDPR article: one call $0.000497 / 0.27 s vs 13 calls $0.006090 / 2.71 s (12.2×, 10.0×; primitives.md quotes the same cookbook as 11.5× / 9.6× [TS/primitives.md]); 11/13 answers std exactly 0 both ways [TS/cookbooks__parallel_questions.md].
- Re-ranking — BM25 top-30 then one Noul per (query, passage): top-1 5% → 18%, top-10 38% → 62%; 1,200 calls $0.0645 [TS/cookbooks__rerank_typesafe.md].
- Line-by-line search — 218 line ids as Choice options (null descriptions) + Noul "exists" gate; FOUND 0.7 / ABSENT 0.35; caught a 0.86 top-line hit with exists 0.14 [TS/cookbooks__semantic_find.md].
- Structure recovery — two requests per document (16 stitch Nouls, then 62 block questions), 10,211 tokens, 0.8 s; rewording "same paragraph" → "picks up mid-sentence" moved list joins from 0.77–0.91 to 0.05–0.22. Cost printed $0.0003 vs prose $0.0015 (inconsistent; ≈ $0.0004 at list price) [TS/cookbooks__autoformat.md].
- Function calling — 10 functions → 54 questions per command in one request;
Literal→ Choice,list[Literal]→ per-member Nouls,bool→ Noul, plus a "stated?" Noul per argument; call confidence = min of judgments; 14 commands 0.53–1.00 [TS/cookbooks__function_calling.md]. - Skill suggestion — Choice over 182 skills + 3 gate Nouls, then rerank top-3 with fits-Nouls; wrong loads 16.8% → 7.3%, needless 9.8% → 4.0% over 488 requests; 182-option rank 0.16–0.31 s [TS/cookbooks__skill_suggestion.md].
- Knowledge graph entity alignment — 3-level Score + 3 Nouls per pair, 450 pairs: 8.9% sameAs, 11.1% curator, 80% unlinked; round score to nearest level [TS/cookbooks__entity_alignment.md].
- Classifying RAG passages — 4 Nouls per passage; thresholds 0.45/0.55/0.70/0.70, injection tested first; a planted injection ranked #1 by cosine scored 0.99 and was excluded [TS/cookbooks__classifying_rag_passages.md].
- Double-checking citations — one Choice supports/contradicts/says_nothing; AUTO_ACCEPT 0.8 on confidence; accurate 0.93–0.99, unsupported 0.27–0.56 → review; fabrication by substring match, no model call [TS/cookbooks__citation_check.md].
- Guardrails for LLMs — 4 Nouls + 4-level severity Score per message; strict/permissive (0.35 review, 0.70/0.85 action, severity ≥ 2.0 upgrades review to block); DAN 0.98, self-harm 0.96 [TS/cookbooks__llm_guardrails.md].
- SDE cascade — cheap extractor → per-field Noul battery (7 heads) → escalate if any P(wrong) > 0.7; a fabricated field fired
hallucinated0.95 while the holistic judge scored 0.56 [TS/cookbooks__sde_cascade.md]. - Date extraction — 7 Choices read parts, code does calendar math; stated dates 0.91–0.97; REVIEW_BELOW 0.60 [TS/cookbooks__date_extraction_cookbook.md].
- Pre-parsed value extraction — regex over-finds spans, Choice picks one plus
none; address 0.98, sender 1.00, credit-vs-charge Noul 0.01/0.99 [TS/cookbooks__pre_parsed_value_extraction_cookbook.md]. - Hierarchical classification — one Choice per node with
c0..cNkeys, beam K=3, geometric-mean path score; beam 4/4 vs greedy 2/4;RetryPolicy(max_retries=5, backoff_initial=1.0, backoff_max=20.0)[TS/cookbooks__hierarchical_classification.md]. - Autoresearch feature discovery — LLM proposes Score/Noul questions, CatBoost on probabilities; held-out RMSE 3.088 → 2.145 (direct Score) → 1.772 (38 questions); 11 Score levels → server error [TS/cookbooks__autoresearch_feature_discovery.md].
- Classification using confidence — 75-option Choice on 60 10-K filings; confidence ≥ 0.9 → 27/30 correct, below → 12/30 as groups but 70% at the parent division [TS/cookbooks__classification_using_confidence.md].
7. Known limits and jaggedness (jev-1.13)
From the jaggedness page, reviewed 2026-09-17 [TS/model-jaggedness__jev-1.13.md], plus community reports:
- Literal reading — answers the question as written. When you find yourself explaining what you "really meant", that explanation is the missing half of the instruction.
- Counting and arithmetic — unreliable, error grows with size; count in code with one Noul per candidate. Numeric representations underperform semantic ones. Do not interpolate a Score to reconstruct a magnitude.
- Dates — read as text, not ordered quantities; extract components as closed-set Choices with a "not stated" option and do arithmetic in code [TS/cookbooks__date_extraction_cookbook.md].
- Indirection — weak; name the relevant state fields directly.
- Large irrelevant state — lowers accuracy ("context rot"); filter in code first, or use a Noul relevance filter [TS/model-jaggedness__jev-1.13.md] [TS/concepts__how-to-build-with-system-one.md].
- Adversarial content — state is data, not hostile; injected instructions can move the answer. Mitigation is precise criteria and edge-case testing; an injection Noul "is a filter... Nothing here is a security boundary" [TS/cookbooks__classifying_rag_passages.md].
- Contradictory instructions vs criteria — a Noul whose
truemaps to "no" performs worse. - No structural invariants — P(x) + P(not x) ≠ 1; Noul and yes/no Choice disagree; Choice is relative, Noul absolute.
- No generation — chaining Choices to force generation is slow and poor; generate candidates elsewhere and let Jev pick.
The page promises "many of these will be fixed in later versions" and says to "test your integration thoroughly before deploying it to many users" [TS/model-jaggedness__jev-1.13.md]; Cloudflare moves versions for you [TS/models.md]. Keep an edge-case regression set (adversarial injections, negations, boundary cases, the P(x)+P(not x) pairs you rely on) with recorded answers and model ids, and re-run it whenever response.model changes, not just the thresholds. For unit tests, stub env.AI.run with recorded fixtures: wrangler dev hits the real account and incurs charges [CF/workers-ai__get-started__workers-wrangler.md].
Also: text only [TS/models.md]; non-English incl. CJK accepted with lower accuracy — test and watch confidence [TS/concepts__state.md] [TS/models.md]; Choice "works reliably up to roughly 240 options" despite the 255 cap [TS/cookbooks__classification_using_confidence.md]; a Score measuring two things at once returned 0% confidence in one community project [https://github.com/vincenth19/jev-testing]; structured level examples help only when they resemble real inputs — a mismatched example returned the same result as plain strings [TS/primitives__score.md]. The jaggedness page's snippet uses TypeSafeClient(model="jev-1.13") and result.nouls[...], while models.md lists only jev-1.13.0/jev-latest/jev-preview; do not copy jev-1.13 unverified [TS/model-jaggedness__jev-1.13.md] [TS/models.md].
Prompt-writing rules the docs recommend
- State the exact condition in
instructions; put boundary cases incriteria; split unavoidable interpretation into two literal questions combined in code [TS/model-jaggedness__jev-1.13.md]. - One condition per Noul; HIGH = yes ("Is the message free of PII?" inverts the signal); a statement works as well as a question [TS/primitives__noul.md].
- Name the narrowest fact that decides the threshold ("picks up mid-sentence", not "same paragraph") [TS/cookbooks__autoformat.md]; ask about the idea, not the parameter name [TS/cookbooks__function_calling.md].
- Score levels: one dimension, descriptive, low to high, no numbers or relative words; rare extremes get their own level [TS/primitives__score.md].
- Choice: full option list; add
other/none of the abovewhen coverage is uncertain; same structured field names across options; contrastivenot_foron confusable pairs [TS/primitives__choice.md] [TS/concepts__how-to-build-with-system-one.md]. - Prefer a JSON-object state with descriptive field names; include only relevant context; facts in
state, judgments inquestions; values from code in their own field, not string-templated [TS/concepts__state.md] [TS/concepts__how-to-build-with-system-one.md]. - Keep questions and threshold constants in one file; "Agents aren't great at writing questions, so expect to edit collaboratively" [TS/agent-skill.md].
- Verifier Nouls:
true= the bad/escalate case; decompose per field and aggregate with max; a holistic "is this good?" gives mushy scores [TS/cookbooks__sde_cascade.md]. - Iterating on questions: draft and tune wording in TypeSafe's Playground (
console.typesafe.ai/playground, TypeSafe login, separate from Cloudflare) or via cookbook share links (the parallel-questions cookbook packs the article and 13 questions into one) [TS/introduction__quickstart.md] [TS/primitives.md] [TS/cookbooks__parallel_questions.md]; no Cloudflare playground for Jev is documented. Both routes servejev-1.13.0[CF/ai__models__typesafe__jev.md], so wording results should transfer; confirm one representative request throughenv.AI.runbefore committing thresholds, since the Cloudflare route adds its own envelope and gateway features but not a different model.
8. Gotchas checklist
modelgoes outsideinput; schema isadditionalProperties: false[CF/jev-schema-input.json].- Path-style
/ai/run/typesafe/jev→ 400; body form only (community). - First call → 402 code 2021 (community, unconfirmed by Cloudflare docs) until AI Gateway credits are loaded [CF/ai-gateway__usage__rest-api.md, Authentication note].
- 5% fee on credit purchases; no inference markup [CF/ai-gateway__features__unified-billing.md].
- Token permission docs conflict; use Workers AI Read+Edit and AI Gateway Read+Edit (4.5).
- Token secret shown once; verify via
/user/tokens/verify[CF/fundamentals__api__get-started__create-token.md]. - REST evaluation sits at
result.result(community); binding shape uncaptured; unwrap defensively. - Noul has no
confidence[CF/jev-schema-output.json]. confidence≠ top probability (0.87 → 0.8) [CF/ai__models__typesafe__jev.md].- Score is zero-indexed and fractional; keys are strings
"0","1"[CF/jev-schema-output.json]; normalise bylevels − 1[TS/primitives__score.md]. - Choice always picks a winner; pair with an absolute Noul [TS/cookbooks__semantic_find.md].
- Choice option order affects results [TS/cookbooks__hierarchical_classification.md].
- Max 255 options (reliable ≈ 240), 2–10 levels; Cloudflare enforces only
minItems: 2; no maximum question count documented on either side (2.1) [TS/api.md] [TS/cookbooks__classification_using_confidence.md] [CF/jev-schema-input.json]. - Context: 32k (Cloudflare) vs 64k total / 32k state+longest question (TypeSafe); budget 32k [CF/ai__models__typesafe__jev.md] [TS/models.md].
- Overflow undocumented: no tokenizer, no error code, no truncation policy on either side; estimate ≈ 4 chars/token, chunk in code, treat an oversized-request 4xx as a hard failure (2.5) [TS/models.md] [TS/api.md] [CF/jev-schema-input.json].
- No version pinning on Cloudflare; log
response.modeland re-validate thresholds when it changes [TS/models.md]. - Cookbook thresholds were tuned on
jev-1.12; Cloudflare servesjev-1.13.0[TS/cookbooks__parallel_questions.md]. - Questions cannot see each other's answers [TS/primitives.md]; question ids are invisible to the model [TS/api.md].
instructionskey required (nullable); Noulcriteriamay hold onlytrue/false[CF/jev-schema-input.json].- Cloudflare's schema and the TypeSafe JS SDK both allow
state: null; the TypeSafe HTTP API reference lists only string | object | array (required). Sources conflict; do not rely on null [CF/jev-schema-input.json] [TS/sdk__javascript__api__interfaces__SystemOneRequestPayload.md] [TS/api.md]. output_tokensnon-zero but free on direct API; Cloudflare basis unknown [TS/models.md].- Budget ≈ 300 input tokens before your own state and questions (inferred from the API reference's 296–318-token one-question examples; no source states a fixed overhead) [TS/api.md].
- Rate limits undocumented on Cloudflare; cookbook pools run 4–16 workers against TypeSafe direct, one with a comment that the public endpoint rate-limits above roughly eight (4.9) [TS/cookbooks__entity_alignment.md] [TS/cookbooks__consistency_noul_cookbook.md].
- No SDK retry through the binding; retry 408/429/500–599 with backoff, fail fast on other 4xx, no idempotency key needed (4.8) [TS/api.md] [TS/sdk__javascript__api__interfaces__RetryPolicy.md].
- No timeout on
env.AI.runin any source; race it against a 10 s deadline (the SDK default) and treat a timeout as "do not act" (4.8) [TS/sdk__javascript__api__interfaces__TypeSafeClientConfig.md]. - SDK
baseURLworks against TypeSafe-OpenAPI-compatible gateways (Vercel AI Gateway, OpenRouter) but Cloudflare's{model, input}envelope does not fit; untested in any source [TS/sdk__python__usage.md] [TS/sdk__javascript__api__interfaces__SystemOneRequestPayload.md]; SDKdebuglogging prints bodies unredacted [TS/sdk__javascript__api__interfaces__TypeSafeClientConfig.md]. wrangler devbills real usage [CF/workers-ai__get-started__workers-wrangler.md]; Pages Functions bind AI via dashboard only [CF/workers-ai__configuration__bindings.md].- BYOK aliases other than
defaultare ignored onenv.AI.run; TypeSafe as BYOK provider unconfirmed [CF/ai-gateway__features__unified-billing.md]. - No ZDR marker on the Jev page; processor question for proxied traffic unresolved [CF/ai__models__typesafe__jev.md] [TS/legal.md].
- AI Gateway logging stores Jev request/response bodies through
defaultregardless of ZDR; disable request/response logging on the gateway or sendcf-aig-collect-log: falsebefore sending customer data (4.3) [CF/ai-gateway__usage__rest-api.md] [CF/ai-gateway__features__unified-billing.md]. - Non-English input less accurate [TS/models.md]; injection Noul is not a security boundary [TS/cookbooks__classifying_rag_passages.md].
- Keep the TypeSafe key server-side; the JS SDK blocks browser use by default (
dangerouslyAllowBrowser: false) because it would expose the key [TS/sdk__javascript__api__interfaces__TypeSafeClientConfig.md]. A community report (dxos) says direct browser calls fail on CORS; unverified. - The TypeSafe agent skill documents the direct API; convert generated transport code to
env.AI.run; update stale skills withclaude plugin marketplace update typesafe-ai+claude plugin update typesafe@typesafe-ai[TS/agent-skill.md].
9. Sources
Local files: TypeSafe docs
Base: /tmp/claude-1000/-home-stevan-dev/4bdbd082-4e99-49ae-a4be-bacdb74171a7/scratchpad/docs/typesafe/
agent-skill.mdapi.mdconcepts__how-to-build-with-system-one.mdconcepts__state.mdconcepts__system-one.mdconcepts__use-case-map.mdconfidence.mdcookbooks.mdcookbooks__autoformat.mdcookbooks__autoresearch_feature_discovery.mdcookbooks__citation_check.mdcookbooks__classification_using_confidence.mdcookbooks__classifying_rag_passages.mdcookbooks__consistency_choice_cookbook.mdcookbooks__consistency_noul_cookbook.mdcookbooks__date_extraction_cookbook.mdcookbooks__entity_alignment.mdcookbooks__function_calling.mdcookbooks__hierarchical_classification.mdcookbooks__llm_guardrails.mdcookbooks__parallel_questions.mdcookbooks__pre_parsed_value_extraction_cookbook.mdcookbooks__rerank_typesafe.mdcookbooks__sde_cascade.mdcookbooks__semantic_find.mdcookbooks__skill_suggestion.mddemos.mddemos__smart-home.mdintroduction.mdintroduction__coding-agents.mdintroduction__machine-learning-primer.mdintroduction__quickstart.mdlegal.mdmodel-jaggedness__jev-1.13.mdmodels.mdpatterns.mdpatterns__composite-scoring.mdpatterns__confidence-routing.mdpatterns__fan-out.mdpatterns__intent-routing.mdprimitives.mdprimitives__advanced.mdprimitives__choice.mdprimitives__noul.mdprimitives__score.mdsdk__javascript.mdsdk__javascript__changelog.mdsdk__javascript__api__classes__APIError.mdsdk__javascript__api__classes__APIPromise.mdsdk__javascript__api__classes__RateLimitError.mdsdk__javascript__api__classes__TypeSafeClient.mdsdk__javascript__api__functions__choice.mdsdk__javascript__api__functions__noul.mdsdk__javascript__api__functions__score.mdsdk__javascript__api__interfaces__ChoiceQuestion.mdsdk__javascript__api__interfaces__ChoiceResponse.mdsdk__javascript__api__interfaces__NoulResponse.mdsdk__javascript__api__interfaces__RetryPolicy.mdsdk__javascript__api__interfaces__ScoreResponse.mdsdk__javascript__api__interfaces__SystemOneRequest.mdsdk__javascript__api__interfaces__SystemOneRequestPayload.mdsdk__javascript__api__interfaces__SystemOneResult.mdsdk__javascript__api__interfaces__TypeSafeClientConfig.mdsdk__javascript__api__interfaces__WithResponse.mdsdk__javascript__api__type-aliases__EntryType.mdsdk__javascript__api__type-aliases__ScoreLegend.mdsdk__javascript__api__type-aliases__ScoreOf.mdsdk__python.mdsdk__python__changelog.mdsdk__python__usage.mdsdk__python__api__clients__sync.mdsdk__python__api__constants.mdsdk__python__api__exceptions.mdsdk__python__api__retries.mdurls.txt
Local files: Cloudflare docs
Base: /tmp/claude-1000/-home-stevan-dev/4bdbd082-4e99-49ae-a4be-bacdb74171a7/scratchpad/docs/cloudflare/
ai__models__typesafe__jev.mdjev-schema-input.jsonjev-schema-output.jsonai-gateway__usage__rest-api.mdai-gateway__features__unified-billing.mdai-gateway__configuration__bring-your-own-keys.mdai-gateway__get-started.mdworkers-ai__configuration__bindings.mdworkers-ai__get-started__workers-wrangler.mdworkers-ai__get-started__rest-api.mdfundamentals__api__get-started__create-token.mdchangelog__post__2026-08-07-workers-ai-unified-billing.md
URLs
Vendor:
- https://developers.cloudflare.com/ai/models/typesafe/jev/
- https://developers.cloudflare.com/ai/models/typesafe/jev/schema-input.json
- https://developers.cloudflare.com/ai/models/typesafe/jev/schema-output.json
- https://dash.cloudflare.com/?to=/:account/ai/models/typesafe/jev
- https://dash.cloudflare.com/694e4cd3a3b5503da63b1739fb086ce9/ai/models/typesafe/jev (user-supplied account link)
- https://dash.cloudflare.com/profile/api-tokens/
- https://developers.cloudflare.com/ai-gateway/usage/rest-api/
- https://developers.cloudflare.com/ai-gateway/features/unified-billing/
- https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/
- https://developers.cloudflare.com/ai-gateway/configuration/manage-gateway/
- https://developers.cloudflare.com/ai-gateway/get-started/
- https://developers.cloudflare.com/ai-gateway/usage/worker-binding-methods/ (not captured; the binding's full third-argument surface, see 4.3)
- https://developers.cloudflare.com/workers-ai/configuration/bindings/
- https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/
- https://developers.cloudflare.com/workers-ai/get-started/rest-api/
- https://developers.cloudflare.com/workers-ai/platform/pricing/
- https://developers.cloudflare.com/workers-ai/platform/limits/
- https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
- https://docs.typesafe.ai/models.md
- https://docs.typesafe.ai/api.md
- https://docs.typesafe.ai/legal.md
- https://docs.typesafe.ai/confidence.md
- https://docs.typesafe.ai/primitives.md
- https://docs.typesafe.ai/model-jaggedness/jev-1.13.md
- https://docs.typesafe.ai/cookbooks.md
- https://docs.typesafe.ai/agent-skill.md
- https://console.typesafe.ai/keys
- https://console.typesafe.ai/playground
- https://typesafe.ai/legal/data-processing
- https://typesafe.ai/legal/mca
- https://typesafe.ai/legal/privacy-policy
- https://github.com/typesafe-ai/skills/blob/main/skills/typesafe-ai/SKILL.md
- https://api.typesafe.ai/docs/
Community (third-party, not vendor-verified):
- https://github.com/clouatre-labs/decisions-judge-mcp/issues/40
- https://github.com/clouatre-labs/decisions-judge-mcp/issues/42
- https://github.com/clouatre-labs/decisions-judge-mcp
- https://github.com/yottayoshida/jev-intent-review
- https://github.com/yottayoshida/jev-intent-review/issues/34
- https://github.com/gargpratyush/jev-router
- https://github.com/gargpratyush/jev-router/issues/22
- https://github.com/dxos/dxos/pull/13389
- https://github.com/Mumega-com/mupot/issues/1437
- https://github.com/vincenth19/jev-testing
- https://openrouter.ai/docs/guides/community/jev
- https://openrouter.ai/api/v1/models/typesafe/jev-1.13/endpoints
- https://www.videosdk.live/developer-hub/ai/what-is-jev
- https://aisa.one/blog/jev-typesafe-ai-agent-decisions
- https://www.forbes.com/sites/josipamajic/2026/09/19/jev-cuts-ai-decision-costs-100x-and-vercel-cloudflare-rushed-to-add-it/ (via web.archive.org snapshot 20260921071613)
10. Live verification on Cloudflare (2026-09-24, added after the research pass)
Measured by Claude on the CurrencyTransfer account with the reference copilot's real 37-question request (~/dev/jev/scripts/replay_via_cf.py, bisection in the session log):
- Score levels must be strings on Cloudflare. A Score whose
criterialevels are objects (the reference uses{summary, signals[]}per level, which TypeSafe's direct API accepts) makesPOST /ai/runreturn HTTP 500code 2002 "Model execution failed (Failed to parse model output)". Cloudflare's published output schema typeslegendvalues as strings, so the wrapper cannot represent object levels. Flatten each level to one string ("Tense or cold: curt answers; irritation; interrupting"). Nouls and Choices with structuredinstructionsandcriteriaobjects work. - 37 questions per request work once scores are flattened: 16 nouls + 3 scores + 3 choices + 15 speculative phrasing choices, ~25 KB JSON, ~7.5k input tokens, 0.3-1.1 s per request; ids containing
::are accepted. - The 32k context window was not hit at this size; the token budget per request should still be asserted in code.
- Same-day:
@cf/deepgram/nova-3diarisation and Aircall recordings verified (seedocs/research/stt-options.md, live verification section). - Parity with TypeSafe's direct API (2026-09-24,
eval/cf-parity.json): replaying the reference copilot's hand-written "good" call through Cloudflare (typesafe/jev, scores flattened) versus the author's recorded direct-API run: final probability 0.904 vs 0.905, mean |Δp| 0.009 (max 0.090 on one utterance), stage identical on 42/42 utterances, next move identical on 41/42, latency 342 vs 345 ms mean, 322,913 vs 330,109 input tokens (the flattened score criteria are slightly shorter), $0.0136 per 42-utterance call. The route is equivalent for design purposes; pin nothing, but logresponse.model(jev-1.13.0today). - Real CT onboarding call through the same engine: 159 Aircall fragments stitched to 145 utterances (same-speaker gap ≤ 1.5 s), 145 requests, 0 errors, 337 ms mean / 388 ms p95, 1,096,933 input tokens, $0.046. Most requests were spent on backchannels ("Yeah.", "Mhmm.", "Okay."); the CT stitcher should merge or skip backchannels so a 10-minute call costs ~$0.015, not $0.05.