Create typed decisions with TypeSafe Jev
curl --request POST \
--url https://nano-gpt.com/api/v1/decisions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"state": "<string>",
"questions": {},
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>"
}
'import requests
url = "https://nano-gpt.com/api/v1/decisions"
payload = {
"state": "<string>",
"questions": {},
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
state: '<string>',
questions: {},
provider: {},
session_id: '<string>',
trace: {},
user: '<string>'
})
};
fetch('https://nano-gpt.com/api/v1/decisions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nano-gpt.com/api/v1/decisions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'state' => '<string>',
'questions' => [
],
'provider' => [
],
'session_id' => '<string>',
'trace' => [
],
'user' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://nano-gpt.com/api/v1/decisions"
payload := strings.NewReader("{\n \"state\": \"<string>\",\n \"questions\": {},\n \"provider\": {},\n \"session_id\": \"<string>\",\n \"trace\": {},\n \"user\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://nano-gpt.com/api/v1/decisions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"state\": \"<string>\",\n \"questions\": {},\n \"provider\": {},\n \"session_id\": \"<string>\",\n \"trace\": {},\n \"user\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://nano-gpt.com/api/v1/decisions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"state\": \"<string>\",\n \"questions\": {},\n \"provider\": {},\n \"session_id\": \"<string>\",\n \"trace\": {},\n \"user\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"model": "<string>",
"answers": {},
"usage": {
"input_tokens": 1,
"output_tokens": 1,
"cost": 1
},
"id": "<string>",
"provider": "<string>"
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"param": "<string>"
}
}Endpoint Examples
Jev Decisions
Call TypeSafe Jev for typed choices, scores, and yes/no probabilities.
POST
/
v1
/
decisions
Create typed decisions with TypeSafe Jev
curl --request POST \
--url https://nano-gpt.com/api/v1/decisions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"state": "<string>",
"questions": {},
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>"
}
'import requests
url = "https://nano-gpt.com/api/v1/decisions"
payload = {
"state": "<string>",
"questions": {},
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
state: '<string>',
questions: {},
provider: {},
session_id: '<string>',
trace: {},
user: '<string>'
})
};
fetch('https://nano-gpt.com/api/v1/decisions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nano-gpt.com/api/v1/decisions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'state' => '<string>',
'questions' => [
],
'provider' => [
],
'session_id' => '<string>',
'trace' => [
],
'user' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://nano-gpt.com/api/v1/decisions"
payload := strings.NewReader("{\n \"state\": \"<string>\",\n \"questions\": {},\n \"provider\": {},\n \"session_id\": \"<string>\",\n \"trace\": {},\n \"user\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://nano-gpt.com/api/v1/decisions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"state\": \"<string>\",\n \"questions\": {},\n \"provider\": {},\n \"session_id\": \"<string>\",\n \"trace\": {},\n \"user\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://nano-gpt.com/api/v1/decisions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"state\": \"<string>\",\n \"questions\": {},\n \"provider\": {},\n \"session_id\": \"<string>\",\n \"trace\": {},\n \"user\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"model": "<string>",
"answers": {},
"usage": {
"input_tokens": 1,
"output_tokens": 1,
"cost": 1
},
"id": "<string>",
"provider": "<string>"
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"param": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"param": "<string>"
}
}Overview
TypeSafe Jev is a decision model. Instead of generating prose, Jev answers named, typed questions about the state you provide:choiceselects one named option and returns a probability for every option.scorereturns an expected score over an ordered rubric.noulreturns the probability that a yes/no statement is true.
POST /api/v1/decisions endpoint. If you already use the official TypeSafe SDK, point it at NanoGPT’s POST /api/v1/systemone compatibility endpoint. Jev is also available through NanoGPT’s OpenAI-compatible Chat Completions and Responses APIs and the Anthropic-compatible Messages API.
Jev returns calibrated probabilities, not generated text. Use a chat model when you need an explanation or other free-form response.
Models
| Model | Use |
|---|---|
typesafe/jev-1.13 | Pinned Jev 1.13 release for integrations that should not follow alias upgrades. |
typesafe/jev-latest | Moving alias for the latest Jev release. |
jev-1.13 and jev-latest. Use the typesafe/... names on the native Decisions and OpenAI/Anthropic-compatible endpoints.
Native Decisions API
Endpoint
POST https://nano-gpt.com/api/v1/decisions
Authorization: Bearer YOUR_API_KEY or x-api-key: YOUR_API_KEY.
Example request
curl https://nano-gpt.com/api/v1/decisions \
-H "Authorization: Bearer $NANOGPT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-1.13",
"state": {
"ticket": "I was charged twice and need this fixed before Friday.",
"customer_tier": "business"
},
"questions": {
"route": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices, refunds, or duplicate charges",
"technical": "Product behavior, bugs, or integrations",
"sales": "Plans, pricing, or purchasing"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is the ticket?",
"criteria": [
"No time pressure",
"Can wait several days",
"Needs attention within one business day",
"Immediate action is required"
]
},
"needs_human_review": {
"type": "noul",
"instructions": "Does this ticket need human review?",
"criteria": {
"true": "A person should review the case",
"false": "Automation can safely handle the case"
}
}
},
"user": "customer_123"
}'
const response = await fetch("https://nano-gpt.com/api/v1/decisions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NANOGPT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "typesafe/jev-1.13",
state: {
ticket: "I was charged twice and need this fixed before Friday.",
customer_tier: "business",
},
questions: {
route: {
type: "choice",
instructions: "Which team should handle this ticket?",
criteria: {
billing: "Payments, invoices, refunds, or duplicate charges",
technical: "Product behavior, bugs, or integrations",
sales: "Plans, pricing, or purchasing",
},
},
urgency: {
type: "score",
instructions: "How urgent is the ticket?",
criteria: [
"No time pressure",
"Can wait several days",
"Needs attention within one business day",
"Immediate action is required",
],
},
needs_human_review: {
type: "noul",
instructions: "Does this ticket need human review?",
criteria: {
true: "A person should review the case",
false: "Automation can safely handle the case",
},
},
},
user: "customer_123",
}),
});
if (!response.ok) throw new Error(await response.text());
const decision = await response.json();
console.log(decision.answers);
import os
import requests
response = requests.post(
"https://nano-gpt.com/api/v1/decisions",
headers={
"Authorization": f"Bearer {os.environ['NANOGPT_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "typesafe/jev-1.13",
"state": {
"ticket": "I was charged twice and need this fixed before Friday.",
"customer_tier": "business",
},
"questions": {
"route": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices, refunds, or duplicate charges",
"technical": "Product behavior, bugs, or integrations",
"sales": "Plans, pricing, or purchasing",
},
},
"urgency": {
"type": "score",
"instructions": "How urgent is the ticket?",
"criteria": [
"No time pressure",
"Can wait several days",
"Needs attention within one business day",
"Immediate action is required",
],
},
"needs_human_review": {
"type": "noul",
"instructions": "Does this ticket need human review?",
"criteria": {
"true": "A person should review the case",
"false": "Automation can safely handle the case",
},
},
},
"user": "customer_123",
},
timeout=30,
)
response.raise_for_status()
print(response.json()["answers"])
Example response
{
"id": "decision_...",
"model": "typesafe/jev-1.13",
"provider": "TypeSafe",
"answers": {
"route": {
"type": "choice",
"choice": "billing",
"confidence": 0.97,
"probabilities": {
"billing": 0.97,
"technical": 0.02,
"sales": 0.01
}
},
"urgency": {
"type": "score",
"score": 2.63,
"confidence": 0.81,
"legend": {
"0": "No time pressure",
"1": "Can wait several days",
"2": "Needs attention within one business day",
"3": "Immediate action is required"
},
"probabilities": {
"0": 0.01,
"1": 0.05,
"2": 0.24,
"3": 0.70
}
},
"needs_human_review": {
"type": "noul",
"noul": 0.84
}
},
"usage": {
"input_tokens": 126,
"output_tokens": 8
}
}
noul is the probability of the true outcome. A score can be fractional because it is the expected value across the returned score distribution.
Question types
| Type | Required fields | Answer |
|---|---|---|
choice | instructions and a non-empty criteria object | choice, confidence, and probabilities keyed by the supplied labels |
score | instructions and 2-10 ordered criteria entries | Fractional score, confidence, legend, and probabilities keyed from 0 |
noul | instructions; optional criteria.true and criteria.false descriptions | noul, a probability from 0 to 1 |
instructions and criterion descriptions can be strings, non-empty JSON objects, or non-empty arrays. A choice criterion may also be null when its label is self-explanatory. The top-level state can be a string, JSON object, or JSON array. Question names become keys in answers.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | typesafe/jev-1.13 or typesafe/jev-latest. |
state | string, object, or array | Yes | Application state for Jev to evaluate. |
questions | object | Yes | One or more named choice, score, or noul questions. |
user | string | No | Your end-user identifier, up to 256 characters. |
session_id | string | No | Your session identifier, up to 256 characters. |
trace | object | No | Caller-supplied trace metadata. |
provider | object or null | No | Native Decisions routing controls. Usually omit this field. |
provider object can contain endpoint-level order, only, ignore, allow_fallbacks, require_parameters, max_price, zdr, and data_collection controls. These names describe native Decisions endpoints such as TypeSafe; they are not NanoGPT provider IDs. API-key provider restrictions and zero-data-retention requirements still apply.
Official TypeSafe SDK
NanoGPT exposesPOST /api/v1/systemone so the official TypeSafe JavaScript and Python SDKs can call Jev without changing their request types.
// npm install @typesafe-ai/sdk
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient({
apiKey: process.env.NANOGPT_API_KEY!,
baseURL: "https://nano-gpt.com/api",
defaultModel: "jev-latest",
});
const result = await client.systemOne({
state: { ticket: "I was charged twice. Please fix this." },
questions: {
route: choice("Which team should handle this ticket?", {
billing: null,
technical: null,
sales: null,
}),
},
});
console.log(result.answers.route.choice);
# pip install typesafe-sdk
import os
from typesafe_sdk import Choice, TypeSafeClient
with TypeSafeClient(
api_key=os.environ["NANOGPT_API_KEY"],
base_url="https://nano-gpt.com/api",
model="jev-latest",
) as client:
result = client.system_one(
state={"ticket": "I was charged twice. Please fix this."},
questions={
"route": Choice(
instructions="Which team should handle this ticket?",
criteria={
"billing": None,
"technical": None,
"sales": None,
},
),
},
)
print(result.choices["route"].choice)
The SDK’s System One call is supported. The SDK’s model-list method is not, because NanoGPT’s
/api/v1/models response uses the NanoGPT/OpenAI-compatible model-list shape rather than TypeSafe’s model-list shape.OpenAI and Anthropic compatibility
Use these shapes when Jev must fit into an existing OpenAI- or Anthropic-compatible client. The answer object is returned as JSON text, so parse the returned string once.Chat Completions
curl https://nano-gpt.com/api/v1/chat/completions \
-H "Authorization: Bearer $NANOGPT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-latest",
"messages": [
{"role": "user", "content": "I was charged twice. Please fix this."}
],
"stream": false,
"response_format": {
"type": "questions",
"questions": {
"billing": {
"type": "noul",
"instructions": "Is this request about billing?"
}
}
}
}'
choices[0].message.content as JSON.
Responses API
curl https://nano-gpt.com/api/v1/responses \
-H "Authorization: Bearer $NANOGPT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-latest",
"input": "I was charged twice. Please fix this.",
"stream": false,
"store": false,
"text": {
"format": {
"type": "questions",
"questions": {
"billing": {
"type": "noul",
"instructions": "Is this request about billing?"
}
}
}
}
}'
output_text as JSON. The same JSON text is also available in the assistant output item.
Anthropic Messages
curl https://nano-gpt.com/api/v1/messages \
-H "x-api-key: $NANOGPT_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-latest",
"max_tokens": 128,
"messages": [
{"role": "user", "content": "I was charged twice. Please fix this."}
],
"stream": false,
"output_config": {
"format": {
"type": "questions",
"questions": {
"billing": {
"type": "noul",
"instructions": "Is this request about billing?"
}
}
}
}
}'
content[0].text as JSON. max_tokens is accepted for Anthropic SDK compatibility but does not change Jev’s fixed typed output.
Limitations
Jev requests are deliberately narrower than chat generation requests:- Only non-streaming text input in
usermessages is supported on compatibility endpoints. - System, developer, assistant, tool, image, audio, video, and file input is not supported.
- Tools, sampling controls, log probabilities, and reasoning generation controls are not supported.
- Output-token-limit fields are accepted where an SDK requires them, but they do not change Jev’s fixed typed output.
- BYOK and accountless x402 payments are not supported.
- Standard NanoGPT or
X-Providerprovider pins are not supported. Use the native Decisionsproviderobject only when you need endpoint-level routing controls.
400 error instead of being silently ignored.Authorizations
bearerAuthapiKeyAuth
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
application/json
Available options:
typesafe/jev-1.13, ~typesafe/jev-latest, typesafe/jev-latest Application state to evaluate.
Show child attributes
Show child attributes
Optional native Decisions endpoint-routing controls. These are not NanoGPT provider IDs.
Maximum string length:
256Maximum string length:
256