Create typed decisions with a decision model
curl --request POST \
--url https://nano-gpt.com/api/v1/decisions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"state": "<string>",
"questions": {},
"independent": true,
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>"
}
'import requests
url = "https://nano-gpt.com/api/v1/decisions"
payload = {
"state": "<string>",
"questions": {},
"independent": True,
"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: {},
independent: true,
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' => [
],
'independent' => true,
'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 \"independent\": true,\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 \"independent\": true,\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 \"independent\": true,\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
Decisions
Typed choices, scores, and yes/no probabilities from decision models.
POST
/
v1
/
decisions
Create typed decisions with a decision model
curl --request POST \
--url https://nano-gpt.com/api/v1/decisions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"state": "<string>",
"questions": {},
"independent": true,
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>"
}
'import requests
url = "https://nano-gpt.com/api/v1/decisions"
payload = {
"state": "<string>",
"questions": {},
"independent": True,
"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: {},
independent: true,
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' => [
],
'independent' => true,
'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 \"independent\": true,\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 \"independent\": true,\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 \"independent\": true,\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
Decision models answer named, typed questions about the state you provide instead of generating prose: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. Decision models are also available through NanoGPT’s OpenAI-compatible Chat Completions and Responses APIs and the Anthropic-compatible Messages API.
Only input tokens are billed. Browse every decision model, with current pricing, on nano-gpt.com/models/text?output=decisions.
Decision models are currently available on
https://nano-gpt.com, including the Decisions and System One endpoints and decision-model requests through compatible chat APIs. The direct API host does not yet list decision models or serve the Decisions routes. Use the website host for decision models until the direct host is updated.Decision models return calibrated probabilities, not generated text. Use a chat model when you need an explanation or other free-form response.
Models
| Model | Context | Use |
|---|---|---|
typesafe/jev-1.13 | 32K | TypeSafe Jev 1.13, pinned for integrations that should not follow alias upgrades. |
typesafe/jev-latest | 32K | Moving alias for the latest TypeSafe Jev release. |
llmtech/decider-0.8b-fp8 | 32K | Mapika’s Decider 0.8B, quantized and hosted by LLM Tech. |
llmtech/decider-2b-fp8 | 32K | Mapika’s Decider 2B, quantized and hosted by LLM Tech. |
llmtech/decider-4b-nvfp4 | 32K | Mapika’s Decider 4B, quantized and hosted by LLM Tech. |
liquid/d1 | 64K | Liquid AI’s D1. |
cloudflare/clef | 16K | Cloudflare’s open decision model. |
llmtech/ prefix, and Clef accepts clef. The System One compatibility endpoint also accepts the official SDK model names jev-1.13 and jev-latest. In GET /api/v1/models?detailed=true, decision models have architecture.output_modalities: ["decisions"] and a supported_endpoints list.
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.
Model-specific rules
| Rule | Applies to |
|---|---|
Choice questions need 2-255 options and Score questions 2-10 levels. A request can contain at most 1,024 scoring rows (otherwise 413 too_many_questions). | Decider |
isolated on a Score question (default true) evaluates each level as its own row, repeating the instructions per level. Set false to evaluate the question as one row. | Decider |
A Noul question may omit instructions when criteria.true or criteria.false describes the answer. | Decider |
Noul questions need instructions, and criteria needs both true and false when it is sent. | Jev, Liquid D1, Clef |
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | A decision model from the table above. |
state | string, object, or array | Yes | Application state for the model 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. |
independent | boolean | No | Decider only. Decider always evaluates questions independently, so send true or omit it; other models reject the field. |
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. Decider and Clef call their decision endpoints directly and accept only zdr and data_collection.
Official TypeSafe SDK
NanoGPT exposesPOST /api/v1/systemone so the official TypeSafe JavaScript and Python SDKs can call any decision model without changing their request types. Set the model to jev-latest, jev-1.13, decider-2b-fp8 (or another Decider), liquid/d1 or clef.
// 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 a decision model 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 the fixed typed output.
Limitations
Decision-model 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 the 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
Decision model ID. Bare Decider IDs and clef are aliases of the prefixed IDs.
Available options:
typesafe/jev-1.13, ~typesafe/jev-latest, typesafe/jev-latest, llmtech/decider-0.8b-fp8, llmtech/decider-2b-fp8, llmtech/decider-4b-nvfp4, decider-0.8b-fp8, decider-2b-fp8, decider-4b-nvfp4, liquid/d1, cloudflare/clef, clef Application state to evaluate.
Show child attributes
Show child attributes
Decider only. Decider always evaluates questions independently, so send true or omit the field; other models reject it.
Optional native Decisions endpoint-routing controls. These are not NanoGPT provider IDs. Decider and Clef accept only zdr and data_collection.
Maximum string length:
256Maximum string length:
256