How we reviewed this: we ran the project’s own commands in node:24-slim — pnpm install --frozen-lockfile, pnpm test, pnpm build — then drove the real Jev client against a stand-in network and logged every request, then started the app itself with no network at all and exercised POST /api/ask. We made no Jev calls and no Search1API calls.
What it is
Search the web by describing what you want. Jev reads the request and the application uses those judgements to choose the query, the sources and the time range; the results come back with visible relevance scores and editable filters. There is no generated answer on top, which is the point.
The README says whose it is without being asked: “Built by Search1API. This is an independent project, not an official TypeSafe product.” It also says the committed KV namespace id and Cloudflare Analytics token belong to the hosted demo, and that the rate limit “is not a global spending cap” — three things most repositories leave you to discover.
One request decides the whole search
We asked it to interpret “Rust async runtimes on Hacker News this month” with three query candidates. One request left the machine, carrying fifteen questions:
questions: 15 = window source_google source_duckduckgo source_yandex source_hackernews
source_reddit source_github source_x source_arxiv source_wikipedia
source_imdb source_wechat source_youtube query entity
state: {"request":"Rust async runtimes on Hacker News this month","now":"2026-09-22",
"candidates":{"c0":"Rust async runtimes","c1":"rust async runtime comparison","c2":"Rust"}}
One choice for the time window, one yes/no per source, and two more choices picking which candidate string to send to a search engine and which is the bare entity name for a catalogue like IMDb. The state is three fields: what you typed, today’s date, and strings the application derived locally. That is the whole thing.
Ranking is a second shape. Results are batched at forty per request, one yes/no question each — we fed it ninety-five results and watched three requests go out carrying 40, 40 and 15 questions, with all ninety-five scored.
Three providers, one dialect boundary
Jev can come from TypeSafe, Vercel’s AI Gateway or a Cloudflare Workers AI binding, and the rest of the application never learns which. The chain falls through in one direction only. Driving systemOne with a two-provider chain and breaking the first:
| first provider returned |
what happened |
| 429 rate limited |
second provider answered |
| 402 out of credit |
second provider answered |
| 503 unavailable |
second provider answered |
| 400 bad request |
threw, one call only |
| 401 bad key |
threw, one call only |
A malformed request or a bad key is not retried somewhere else, which is right: it would fail there too, and you would pay twice to learn it. The Vercel path translates in both directions — we watched a noul question go out as type: "boolean" under the evaluation-model spec headers, and the probability: 0.77 come back as {"type":"noul","noul":0.77} with confidence lifted out of providerMetadata.
Provider error bodies never reach the caller. We had the stand-in return a 500 whose body contained both our fake key and a plausible private query:
500 -> "Jev is temporarily unavailable. Please try again shortly." leaks canary: false
429 -> "Jev is receiving too many requests. Please try again shortly."
403 -> "Jev could not process this request (HTTP 403)."
The comment above that function explains why: “Provider error bodies are implementation details and may contain request data.”
The app’s own edges
Running offline with junk keys, POST /api/ask behaved as documented:
| request |
response |
no Origin header |
403 {"error":"forbidden"} |
Origin: https://evil.test |
403 |
| a 301-character query |
400, with the limit in the message |
| thirteen sources |
400 s must contain at most 12 entries |
twelve duplicate google entries |
accepted, merged to one |
| same origin, no provider reachable |
200, then one NDJSON error event |
The source list is bounded before duplicates are filtered, so a long list of repeats cannot multiply the ranking calls it would pay for. That is a small thing that a lot of search front ends get wrong.
Where your query goes
Two vendors, and the README says so plainly: the Jev provider reads the request, and Search1API runs the search derived from it. For ranking, result titles and snippets go to the Jev provider too. The application itself “does not store queries or result clicks”. Keys live in .dev.vars locally and in Wrangler secrets in production.
One rough edge
The documented local quickstart does not work as written. wrangler.jsonc declares the Workers AI binding as remote: true with a comment saying it is “idle unless JEV_PROVIDERS lists cloudflare” — but the Vite plugin opens a remote proxy session at startup regardless, so pnpm dev stops with “No credentials found” even when you configured TypeSafe alone. Commenting out that single line is enough; the server then runs entirely offline. Reported.
Everything else in the local path is clean. 16 test files and 115 tests pass with no keys and no network, pnpm build type-checks as part of the build, and the lockfile installs frozen.
Verdict
The clearest answer in this directory to “what does a decision model actually replace”. Not the search, and not the writing — the dozen small judgements between you typing a sentence and a query reaching an engine, which usually become either a prompt or a pile of regexes. Here they are fifteen typed questions in one request, and you can read every one of them in typesafe.ts.
Use the hosted demo to see whether you like search without an answer on top. Self-host it only with your own keys, your own KV id and your own analytics token — and comment out the ai binding first.
For search of a codebase instead of the web, see Blink; for ranking inside a graph, neo4jev.
See how it compares with other tools in Best Jev tools, tested hands-on.
Review updated Sep 22, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.