# Vansa > Vansa is a decision model API. Send text or JSON and typed questions; get calibrated probabilities for every option you define, with no generated text. The model (vansa-3: about 4B parameters, 16,384 tokens of context per question; JevK5, a Qwen3.5-4B fine-tune, plus Vansa's email skill) reads its own probability for each of your options instead of generating text, so an answer is always one of your options. It is most accurate on the question sets it was trained on (the presets below), so use a preset whenever the input matches one. - Model names: `vansa-3` is the default; `vansa`, `vansa-latest` and the earlier ids `vansa-2` and `vansa-1` still work and are answered by vansa-3 (responses say `"model": "vansa-3"`). - Status: closed beta, by private invitation only. API keys are issued by the Vansa team; no billing and no SLA yet. - Jev-compatible: the same `state` + `questions` format as TypeSafe's Jev `/v1/systemone`; Jev client code works after changing the base URL, the key and `model`. - Data: request bodies are not stored (each request is logged with time, key, model, question count, tokens, latency, status and client IP, plus a short error message for rejected requests). Requests pass through Cloudflare and are answered on Vansa's own GPU server; no third-party AI provider receives them. This file is for AI coding agents that integrate Vansa. Human documentation: https://docs.vansa.org . The complete documentation as one Markdown file: https://docs.vansa.org/llms-full.txt **Hosts** - API calls only: https://api.vansa.org (for example POST https://api.vansa.org/v1/systemone). Nothing else answers API calls. - Documentation, playground, presets and this file: https://docs.vansa.org - Website: https://vansa.org **Request** POST https://api.vansa.org/v1/systemone Headers: `Authorization: Bearer vsk_...` (keep the key server-side), `Content-Type: application/json`, any `User-Agent` (Cloudflare answers 403 "error code: 1010" to Python-urllib's default one). ```json { "model": "vansa-3", "state": {"subject": "Duplicate charge on invoice #4411", "body": "We were billed twice. Refund it today or we cancel."}, "questions": { "department": {"type": "choice", "instructions": "Which department should handle the request in `body`?", "criteria": {"billing": "invoices, payments, refunds", "technical": "bugs, outages", "other": null}}, "urgency": {"type": "score", "instructions": "How urgent is this request?", "criteria": ["not urgent", "soon", "critical deadline or blocking issue"]}, "churn_risk": {"type": "noul", "instructions": "Does the customer threaten to cancel?"} } } ``` Question types: - `choice`: `criteria` = {"option": "description or null"} (2 to 128 options) or a list of names. Answer: `{"type": "choice", "choice": "billing", "probabilities": {...}, "confidence": 0.84}`. - `score`: `criteria` = list of levels, lowest first (2 to 32). Answer: `{"type": "score", "score": 1.2 (expected level 0..N-1), "legend": {"0": ...}, "probabilities": {"0": ..}, "confidence": ..}`. Take the argmax of `probabilities` for a single level. - `noul`: yes/no, `criteria` optional {"true": "...", "false": "..."}. Answer: `{"type": "noul", "noul": 0.93 (probability of yes), "probabilities": {"false": .., "true": ..}, "confidence": ..}`. Response: `{"id": "req_...", "object": "systemone.result", "model": "...", "created": 1790000000, "answers": {: ...}, "usage": {"input_tokens": .., "output_tokens": 0, "state_tokens": .., "truncated": false}, "latency_ms": ..}`. `usage.truncated` is always false: a request over the context limit is refused with 400, never cut. `confidence`: for choice and score, 1 minus the normalized entropy (0 = uniform, 1 = certain); for noul, the larger of the two probabilities (0.5 to 1). **Limits and errors** - 32 questions per request, 128 options per choice, 32 levels per score, 4,000 characters per instruction, 512 KB per body. - Context: 16,384 tokens per question, counted on the whole prompt (state + instructions + options + about 100 tokens of fixed prompt). A longer request gets 400 (`invalid_request_error`); nothing is cut. Accuracy is validated on states up to about 4,000 tokens. - Up to 16 options are answered in one pass; with more, groups of up to 16 and a final between the group winners (slower, and on the 18-, 60- and 77-option benchmarks less accurate than Vansa-2). Keep descriptions to one short sentence. - Latency: each question is its own pass, so time grows with the number of questions, the length of the state and options beyond 16. Requests may wait in a short queue during bursts. Use a client timeout of at least 60 s. - Per-key limits: see GET https://api.vansa.org/v1/me (null = unlimited). - Retry with backoff (honour `Retry-After` when present): 429 = rate limit or daily quota, 503 = model loading or server busy, 502 = the model server is unreachable, restarting, too slow or failed on the request, other 5xx. Do not retry: 400 = bad request (`error.param` names the field when there is one; the message names the question for a prompt over 16,384 tokens), 401 = missing/invalid/revoked key, 404 = unknown endpoint or model (`code` = `model_not_found`), 413 = body over 512 KB (`code` = `request_too_large`). Cloudflare's own errors (403 "error code: 1010" as plain text, 52x) are not JSON. - Errors are JSON: `{"error": {"type": "...", "message": "...", "param": "...", "code": "..."}}`. API responses carry an `X-Request-Id` header (an unexpected 500 gives the id in its message; Cloudflare's own errors have none). - Deterministic: the same request gives the same answers. **Presets (use these when the input matches)** A preset is a question set the model was trained on, with the input format it was trained with. Send its questions unchanged: same ids, option names and descriptions. Rewording or renaming options costs accuracy. **Email qualification: link-building / guest-post outreach threads** Use it when the input is an email thread between an outreach team (the buyer of a sponsored guest post or link) and one website. It answers: did they reply (reply), what happened (outcome), how the price went (negotiation, they_named_price, rounds, price_for_igaming), their terms (dofollow, permanent, sponsored_tag_required, prepayment_demanded, article_by_them_only, links_allowed), why it failed (fail_reason), how well the outreach side handled it (our_handling) and 12 specific mistakes (mistake_*). 26 more `extra_questions` cover facts such as payment method, tone, price band, currency and last topic. Measured on 600 held-out threads: 96.8% agreement with the reference labels on the 26 questions (always answering the most common option would score 81.8%), 95.4% on the extra questions; calibration error 0.009. How to call it: 1. Download the preset (send a User-Agent): https://docs.vansa.org/presets/email-qualification.json (fields: `model`, `questions`, `extra_questions`, `extra_questions_only_when`, `example_state`, `state_format`, `how_to_use`, `measured.per_question`, `measured.weakest`). 2. Build `state` per thread with the reference builder https://docs.vansa.org/presets/email_state.py (Python standard library; `build_state(site, subject, emails, now=None)` with emails `{"from": "us"|"them", "date", "text"}` plus optional `subject`, `sender`, `headers` so it can mark auto-replies and bounces). If you cannot run Python, reproduce its output exactly: `{"site", "subject", "roles", "counts": {"ours", "theirs", "auto_replies", "bounces", "days_since_last", "last_from"}, "messages": [{"n", "from": "us"|"them", "date": "YYYY-MM-DD", "text", "kind"?}]}` in this key order, sent as a JSON object (not a string). - `roles` is exactly: "us = our outreach team buying a sponsored guest post; them = the website owner or editor" (also `preset.example_state.roles`). `us` is always the outreach side. - `counts` cover the whole thread and come from the mail headers and the current date, computed by code: `theirs` excludes auto-replies and bounces; `auto_replies` counts every message with an auto-reply signal, including bounces that carry one; `days_since_last` = whole days since the last message; `last_from` = who sent the last message. - `text` = the new text of each email only (quoted replies, `>` lines, mail footers and anything after a line of only 2 or 3 dashes or a "Sent from my iPhone/iPad/Samsung/Android" line removed), at most its first 150 words, shortened to the first 120 plus up to 3 later sentences (35 words at most) that carry an amount or a yes/no answer, with " ..." where text was cut; fewer words when a long thread must be trimmed; `kind` = "auto_reply" or "bounce" when it applies. - Long threads: keep the first message, their first real reply and the last 4, and put `{"n": "...", "omitted": k}` in place of the rest; the whole state under about 1,250 tokens. 3. POST `{"model": preset.model, "state": state, "questions": preset.questions}`; if you need the extras, a second POST with `preset.extra_questions` and the same state (26 + 26 > 32 per request). 4. Read answers with the argmax rule (choice: `choice`; score: argmax of `probabilities`; noul: `noul` >= 0.5). Use `confidence` or the probabilities to route low-confidence threads to a human. 5. Discard an extra answer when its condition in `preset.extra_questions_only_when` does not hold: tone_of_them, their_last_topic, word_count_bucket, price_band and currency need counts.theirs > 0; they_lowered_price needs a named price; price_came_down needs a named and an agreed price; igaming_costs_more needs both a normal and an iGaming price. The 95.4% was measured only where the condition holds. payment_method was trained and measured only on threads where the reference labels had a value ("none" included: 378 of the 600 test threads); nothing in the state shows which threads those are, so its agreement covers only those threads. Do not use Vansa for: price amounts, counter-offers, free-text notes or a suggested reply; extract numbers with code or an LLM. 84% of the training threads were English (then Spanish and Portuguese), and Vansa-3's base model (JevK5) is documented by its author as English only. **Writing your own questions** For inputs that no preset covers: send the state as JSON with named fields and refer to them in backticks (`body`); ask one fact per question; describe every option in one short sentence; include an `other` / `none` option; batch related questions in one request. Accuracy on new questions is lower than on presets, so validate on a sample of your own data first. ## Docs - [Documentation](https://docs.vansa.org): quickstart, reference, email preset, errors, limits - [Full documentation, Markdown](https://docs.vansa.org/llms-full.txt): the same content as one file - [Email qualification preset](https://docs.vansa.org/presets/email-qualification.json): questions, input format, example, measured accuracy - [Email state builder, Python](https://docs.vansa.org/presets/email_state.py): builds the trained input format from an email thread - [OpenAPI schema](https://api.vansa.org/openapi.json): request and response schema of api.vansa.org ## Optional - [Playground](https://docs.vansa.org/playground): try requests in the browser - [Preset list](https://docs.vansa.org/presets/index.json) - [Vansa-3](https://docs.vansa.org/#vansa-3): what changed from Vansa-2 (4B parameters, 16k context, same API) - [Benchmarks](https://docs.vansa.org/#benchmarks): an estimated 0.871 on 106 never-trained questions (Vansa-2 0.712, Jev 0.949); JevBench public items 80.1 (Jev 82.3); lower than Vansa-2 on 11 of the 13 public classification tasks Vansa-2 was trained on (mean 0.709 vs 0.805) and on jailbreak detection (0.915 vs 0.975), so test those use cases on your own data - [API catalog, RFC 9727](https://api.vansa.org/.well-known/api-catalog) - [Models](https://api.vansa.org/v1/models) - [Website](https://vansa.org)