Create typed decisions with a decision model
curl --request POST \
--url https://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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 work on both API hosts. The examples use the direct API host,
https://api.nano-gpt.com; https://nano-gpt.com accepts the same requests. See API Hosts.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://api.nano-gpt.com/api/v1/decisions
Authorization: Bearer YOUR_API_KEY or x-api-key: YOUR_API_KEY.
Example request
curl https://api.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://api.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://api.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://api.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://api.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://api.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://api.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://api.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