# Jein API docs > The full text of https://jein.dev/docs/ in one file. Summary and links: https://jein.dev/llms.txt # Jein quickstart Source: https://jein.dev/docs/ Send a support ticket once, then ask which team should handle it, how urgent it is, and whether the customer asks for a refund. The API returns a named, typed answer for each question. ## Set up your client [Create an API key](https://app.jein.dev/app/keys) and export these two environment variables. Use the service root as the base URL; the SDK adds the versioned path. ```bash export TYPESAFE_BASE_URL=https://app.jein.dev export TYPESAFE_API_KEY=YOUR_JEIN_API_KEY ``` The examples below use the same variables and request in every language. Python requires `typesafe-sdk==0.7.1`; JavaScript requires Node.js 20+ and `@typesafe-ai/sdk@0.6.0`. ```bash python3 -m pip install typesafe-sdk==0.7.1 # Or, for a JavaScript project: npm install @typesafe-ai/sdk@0.6.0 ``` ## Make your first request `POST /v1/systemone` evaluates the three questions together. Choose a language, copy the example, and run it after setting your API key. Using a coding agent? Paste the [Coding agent](#coding-agent) prompt instead: it points the agent to [/llms.txt](https://app.jein.dev/llms.txt), a plain-text guide to the API, and never contains your key. ### curl ```bash run curl --fail-with-body --silent --show-error "$TYPESAFE_BASE_URL/v1/systemone" \ -H "Authorization: Bearer $TYPESAFE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "laya-latest", "state": "Hi, I was billed twice for March. Please refund the duplicate.", "questions": { "department": { "type": "choice", "instructions": "Which team should handle this?", "criteria": {"billing": "Payment issues", "technical": "Bugs", "sales": "Sales questions"} }, "urgency": { "type": "score", "instructions": "How urgent is this?", "criteria": ["Low", "Medium", "High"] }, "wants_refund": {"type": "noul", "instructions": "The customer asks for a refund"} } }' ``` ### Python ```python run from typesafe_sdk import Choice, Noul, Score, TypeSafeClient # Reads TYPESAFE_BASE_URL and TYPESAFE_API_KEY. with TypeSafeClient() as client: result = client.system_one( model="laya-latest", state="Hi, I was billed twice for March. Please refund the duplicate.", questions={ "department": Choice( instructions="Which team should handle this?", criteria={"billing": "Payment issues", "technical": "Bugs", "sales": "Sales questions"}, ), "urgency": Score( instructions="How urgent is this?", criteria=["Low", "Medium", "High"], ), "wants_refund": Noul(instructions="The customer asks for a refund"), }, ) print(result.model_dump_json(indent=2)) ``` ### JavaScript ```javascript run import { choice, noul, score, TypeSafeClient } from '@typesafe-ai/sdk'; // Reads TYPESAFE_BASE_URL and TYPESAFE_API_KEY. const client = new TypeSafeClient(); const result = await client.systemOne({ model: 'laya-latest', state: 'Hi, I was billed twice for March. Please refund the duplicate.', questions: { department: choice('Which team should handle this?', { billing: 'Payment issues', technical: 'Bugs', sales: 'Sales questions', }), urgency: score('How urgent is this?', ['Low', 'Medium', 'High']), wants_refund: noul('The customer asks for a refund'), }, }); console.log(JSON.stringify(result, null, 2)); ``` ### Coding agent ```prompt Set up this project to call the Jein API. - Read https://app.jein.dev/llms.txt first: it describes the request, the response, the limits and the errors. - Base URL: https://app.jein.dev. Read it from the environment variable TYPESAFE_BASE_URL. - The API key is in the environment variable TYPESAFE_API_KEY. Never print, log or commit it. - Use the TypeSafe SDK: Python typesafe-sdk==0.7.1, or JavaScript @typesafe-ai/sdk@0.6.0 (Node.js 20+). - Plain HTTP also works: POST $TYPESAFE_BASE_URL/v1/systemone with the header "Authorization: Bearer $TYPESAFE_API_KEY". - Use the model laya-latest. GET $TYPESAFE_BASE_URL/v1/models lists every model name. - Make one working call first, then handle 422 validation errors and retry 429 and 529 after Retry-After. ``` ## Read the response Illustrative response, recorded from a real run of the request above. Model updates can shift the numbers slightly, and your own requests will have different token counts. ```json { "model": "laya-0.3.20", "answers": { "department": { "type": "choice", "choice": "billing", "confidence": 0.8725, "probabilities": { "billing": 0.9736, "technical": 0.0147, "sales": 0.0117 } }, "urgency": { "type": "score", "score": 1.1095, "confidence": 0.1797, "legend": { "0": "Low", "1": "Medium", "2": "High" }, "probabilities": { "0": 0.1311, "1": 0.6282, "2": 0.2406 } }, "wants_refund": { "type": "noul", "noul": 0.8793 } }, "usage": { "input_tokens": 44, "output_tokens": 0 } } ``` - `department.choice` is the selected label; `probabilities` contains a probability for every option. - `confidence` is 1 minus the normalized entropy of `probabilities`: 1.0 when all probability sits on one option, 0.0 when the options are equally likely. It is not the top probability, so billing at 0.9736 has confidence 0.8725, and urgency, spread over three levels, has 0.1797. - `urgency.score` is the probability-weighted level from your ordered criteria. It can fall between levels: 1.1095 is Medium, leaning towards High. - `wants_refund.noul` is the probability that the statement is true. Noul has no separate confidence field. - `usage.input_tokens` counts the state once plus instructions and criteria. `output_tokens` is zero. - `model` identifies the model that answered, even when you send the `laya-latest` alias. The TypeSafe SDK default `jev-latest` is accepted too. ## Before sending production traffic Jein currently accepts short inputs. Longer questions leave less room for the state, and oversized requests are rejected rather than truncated. Review the [limits](https://jein.dev/docs/limits/), [migration checklist](https://jein.dev/docs/migrating/), [error reference](https://jein.dev/docs/errors/), and [OpenAPI contract](https://app.jein.dev/openapi.yaml). Check [Usage](https://app.jein.dev/app/usage) after your first successful request. Call the API from your server. A web page on another origin can call it only if that origin is on the service's CORS allow-list, which is empty by default. A key used in a web page is readable by every visitor, and its requests count against your account's allowance: give the page its own key and revoke it on [API keys](https://app.jein.dev/app/keys) when the page no longer needs it. --- # Models and language routing Source: https://jein.dev/docs/models/ Jein runs two pinned checkpoints. `/v1/models` lists every accepted model name; the `model` field in a System One response names the checkpoint that actually ran. | Request model | Selection | Response model | |---|---|---| | `jev-latest`, `jev-preview`, `jev-1.13.0`, `laya-latest` | Detect the language of the state | `laya-0.3.20` for English; `laya-multilingual-0.3.20` for detected non-English | | `laya-0.3.20` | English checkpoint, including non-English states | `laya-0.3.20` | | `laya-multilingual`, `laya-multilingual-0.3.20` | Multilingual checkpoint, including English states | `laya-multilingual-0.3.20` | The `x-jein-routed-language` response header is an ISO 639-1 code such as `en`, `de`, or `fr`; `und` means the text was too short or ambiguous to identify. Routing reads string values of `state`, not JSON field names or the question instructions. It uses a pinned, in-process, CPU-only classifier and makes no network request. Ambiguous text stays on English; choose `laya-multilingual` explicitly if you already know the state is non-English. Pin `laya-0.3.20` to stay on English. ## Language and quality The multilingual model card reports evaluations on about **50 languages**; its “100+ languages” statement is unverified. The local quick check covers German, French and Spanish only: 11/12 correct versus 9/12 for the English checkpoint. On English, the multilingual checkpoint scored 8/12 versus 10/12. The larger, mostly English local corpus scored 59/78 (76%) on accepted short-state answers. These samples are directional, and multilingual confidence is uncalibrated. Review decision thresholds on your own traffic before changing them. ## Per-model limits | Limit | English | Multilingual | |---|---:|---:| | Laya sequence window per question | 512 tokens | 2048 tokens | | Instruction and option head budget | 192 tokens | 256 tokens | | Typical state room | 437–478 tokens | 1975–2014 tokens | | Default processed-token cap per request | 3,500 | 6,000 | | Questions per request | 32 | 32 | The worker rejects input that would be truncated. It bills the same Jev-style input-token count for either model; more questions increase processed compute tokens. Both models draw on the same free monthly allowance. Both checkpoints have a 9-second API deadline, with 500 ms reserved for response and metering. --- # Migrating from Jev Source: https://jein.dev/docs/migrating/ Use this checklist before switching traffic: 1. Set `TYPESAFE_BASE_URL` to your Jein origin and `TYPESAFE_API_KEY` to a Jein API key. Keep your existing `systemOne` or `system_one` call. 2. Use `jev-latest` (the SDK default), `jev-preview`, `jev-1.13.0`, or `laya-latest` for language routing. English resolves to `laya-0.3.20`; detected non-English resolves to `laya-multilingual-0.3.20`. To select a checkpoint explicitly use `laya-0.3.20`, `laya-multilingual`, or `laya-multilingual-0.3.20`. See [models](https://jein.dev/docs/models/). 3. Measure every question's state room. English has a 512-token sequence; the exact room is `511 − len(head + options sequence)` for that question. Simple choice and noul questions leave about 476 state tokens. Multilingual has a 2048-token window and about 1975–2014 state tokens of room. Longer instructions and options reduce either limit. 4. Split long state into smaller, self-contained chunks and send only the relevant chunk per call. Jein rejects state that would truncate; it never silently cuts it. 5. Keep each call at or below 32 questions and the selected model's processed-token cap: 3,500 English or 6,000 multilingual. The processed cost is `number of questions × longest per-question Laya sequence length`, so many questions with long states hit the cap sooner. A 32-question call can exceed the SDK's 10-second default timeout on a busy CPU worker; prefer smaller batches. 6. Handle 402 monthly free allowance exhaustion by checking `/app/usage` and waiting for the UTC reset, or email [support.jein@snblago.com](mailto:support.jein@snblago.com). Jev does not have this response. 7. Handle 429 account rate/in-flight limits and 529 worker busy/startup with `Retry-After` and `retry-after-ms`; branch on `x-jein-error-code`. The SDK retries these by default, so set an overall deadline. 8. Compare outputs: Jein answers carry no Jev `action` object (`action.act_probability`), and noul answers carry no `confidence`; read `noul` directly. Multilingual confidence is uncalibrated, and its English quality was lower in a 12-case check. The two checkpoints use different tokenizers, so token counts and state limits differ. The model card evaluates about 50 languages; its “100+” claim is unverified. 9. Pin SDK versions while migrating: Python `typesafe-sdk==0.7.1`; JavaScript `@typesafe-ai/sdk@0.6.0`. Roll back by restoring the previous Jev base URL and Jev key; keep keys separate. `GET /v1/models` lists every name a request may use, including the `jev-latest` (SDK default), `jev-preview`, and `jev-1.13.0` compatibility aliases, so a client can validate against it. Existing SDK calls and the `/v1/systemone` route are unchanged. To check a state before switching, paste it into the [Sandbox](https://app.jein.dev/app/sandbox) for a token count, or send the request: if the state does not fit, the `state_too_long` error tells you the room each question leaves. Try the migrated request locally: ```bash run curl --fail --silent "$TYPESAFE_BASE_URL/v1/systemone" -H "Authorization: Bearer $TYPESAFE_API_KEY" -H 'Content-Type: application/json' -d '{"model":"jev-latest","state":"Hi, I was billed twice for March. Please refund the duplicate.","questions":{"department":{"type":"choice","instructions":"Which team should handle this?","criteria":{"billing":"Payment issues","technical":"Bugs","sales":"Sales questions"}}}}' | python3 -c 'import json,sys; r=json.load(sys.stdin); assert r["model"] == "laya-multilingual-0.3.20" and "department" in r["answers"]' ``` --- # Limits Source: https://jein.dev/docs/limits/ The values below are the hosted service's. Limits are set by the active deployment configuration; the API error text reports the current value. See [errors](https://jein.dev/docs/errors/) for status codes and the [migration checklist](https://jein.dev/docs/migrating/) for request sizing. ## state_too_long Each question has its own state room: about 437–478 tokens with the English model's 512-token sequence, or 1975–2014 with the multilingual model’s 2048-token sequence. Shorten the state or that question's instructions/options. See [models](https://jein.dev/docs/models/). ## instructions_too_long Reduce the question's instructions or options. ## request_too_large Split the request. Processed tokens are questions multiplied by the longest sequence; the default ceiling is 3,500 for English or 6,000 for multilingual. The API error reports the selected worker's cap. ## options_too_long Shorten option names and descriptions. An option has a 48-token budget. ## too_many_questions Split the call; the maximum is 32 questions per call. ## rate_limited Each account can send a burst of 40 requests at once; after that the allowance refills at 4 per second, which is 240 requests per minute. Every request with a valid API key counts, including requests rejected as invalid (422), so fix a failing request before retrying it in a loop. Before the key is checked, each IP address may send up to 20 requests per second. Both limits answer 429 `rate_limited`; the error text names the limit, and `Retry-After` (seconds) and `retry-after-ms` say when to retry. Honor them. ## too_many_inflight Wait for a running call to finish. The account cap is 4 concurrent calls. ## quota_tokens Your account's free monthly allowance is used up. By default that is an allowance of 500,000 billed input tokens a month; the error text names your account's own allowance. See [usage](https://app.jein.dev/app/usage) for the UTC reset time. The allowance is per account, shared by all its API keys. The [API keys](https://app.jein.dev/app/keys) page shows each key's requests and billed input tokens for the current UTC month. ## quota_compute The internal compute allowance is exhausted. See [usage](https://app.jein.dev/app/usage) for the UTC reset time. --- # Errors Source: https://jein.dev/docs/errors/ Every non-200 response has `x-jein-error-code`. Validation errors use FastAPI-style `detail[]` entries with `loc`; other errors use `{"detail":"message"}`. Never log request state or API keys when diagnosing an error. ## validation_error The request does not match the schema (422): a missing or mistyped field, or an invalid question definition such as an unknown `type`, missing `instructions`, or malformed `criteria`. Each `detail[]` entry names the problem and its `loc`. A body that is not readable JSON answers 400 with the same code. Rejected requests count toward the [rate limit](https://jein.dev/docs/limits/#rate_limited). ## unknown_model Use a name that `GET /v1/models` lists; see [models](https://jein.dev/docs/models/). ## model_retired Switch to `laya-latest`. ## invalid_api_key Check the Bearer key or create a new key. ## overloaded The worker refused admission (529), for example a short refusal during a burst of calls. Honor `Retry-After`; no usage is charged. ## deadline The request did not complete within the end-to-end deadline (529): it expired in the worker's queue, or the worker call timed out. No usage is charged. Retry after `Retry-After`, or send fewer questions per call. The API text is "Request did not complete within the deadline and was not charged. Retry, or send fewer questions per call.", followed by a link to these docs. ## not_ready The worker is loading (529). Retry after the indicated delay. ## body_too_large The JSON body exceeds 1 MiB (413). ## metering_failed The usage write failed (503); no charge was recorded. Retry. ## metering_uncertain The usage commit outcome is uncertain (503). Before retrying, look up the response's `X-Request-Id` in the [request log](https://app.jein.dev/app/usage/requests): if it is listed, the request was recorded; only recorded requests count against your allowance. ## not_found The resource does not exist (404). ## internal_error An unexpected server error occurred (500). Email the request id (`x-typesafe-request-id`) to [support.jein@snblago.com](mailto:support.jein@snblago.com). --- # Changelog Source: https://jein.dev/docs/changelog/ Pin SDK versions in production. ## 2026-09-27: Free preview - Jein opens as a free preview: a free account with 500,000 free input tokens a month, no credit card and no payment details. - Jev-compatible API: `POST /v1/systemone` and `GET /v1/models` take the same requests as Jev, so the Jev SDKs work with a changed base URL and key. - Two pinned checkpoints: English `laya-0.3.20` and `laya-multilingual-0.3.20`. `laya-latest` and the Jev aliases route non-English state text to the multilingual checkpoint; responses name the model that ran and set `x-jein-routed-language`. Pin `laya-0.3.20` to stay on English. - `GET /v1/models` lists every accepted model name, including the Jev compatibility aliases. - A Sandbox in the dashboard runs requests without code.