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>",
"include_usage": false
}
'import requests
url = "https://api.nano-gpt.com/api/v1/decisions"
payload = {
"state": "<string>",
"questions": {},
"independent": True,
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>",
"include_usage": False
}
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>',
include_usage: false
})
};
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>',
'include_usage' => false
]),
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 \"include_usage\": false\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 \"include_usage\": false\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 \"include_usage\": false\n}"
response = http.request(request)
puts response.read_body{
"model": "<string>",
"answers": {},
"usage": {
"input_tokens": 1,
"output_tokens": 1,
"cost": 1,
"estimated_cost": 1
},
"id": "<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>"
}
}Decisions
Typed choices, scores, and yes/no probabilities from decision models.
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>",
"include_usage": false
}
'import requests
url = "https://api.nano-gpt.com/api/v1/decisions"
payload = {
"state": "<string>",
"questions": {},
"independent": True,
"provider": {},
"session_id": "<string>",
"trace": {},
"user": "<string>",
"include_usage": False
}
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>',
include_usage: false
})
};
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>',
'include_usage' => false
]),
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 \"include_usage\": false\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 \"include_usage\": false\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 \"include_usage\": false\n}"
response = http.request(request)
puts response.read_body{
"model": "<string>",
"answers": {},
"usage": {
"input_tokens": 1,
"output_tokens": 1,
"cost": 1,
"estimated_cost": 1
},
"id": "<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.
https://api.nano-gpt.com; https://nano-gpt.com accepts the same requests. See API Hosts.PPLX Decider v1.1, Clef Flash, image input and include_usage are currently available only on https://nano-gpt.com. The direct API host does not support them yet and still serves Clef as a 16K text-only model, so the image examples use https://nano-gpt.com.Models
| Model | Context | Images | Use |
|---|---|---|---|
typesafe/jev-1.13 | 32K | No | TypeSafe Jev 1.13, pinned for integrations that should not follow alias upgrades. |
typesafe/jev-latest | 32K | No | Moving alias for the latest TypeSafe Jev release. |
llmtech/decider-0.8b-fp8 | 32K | No | Mapika’s Decider 0.8B, quantized and hosted by LLM Tech. |
llmtech/decider-2b-fp8 | 32K | No | Mapika’s Decider 2B, quantized and hosted by LLM Tech. |
llmtech/decider-4b-nvfp4 | 32K | No | Mapika’s Decider 4B, quantized and hosted by LLM Tech. |
liquid/d1 | 64K | No | Liquid AI’s D1. |
perplexity/pplx-decider-v1.1-27b | 262K | Yes | Perplexity’s PPLX Decider v1.1 27B. Supports zero data retention. |
cloudflare/clef | 1,800 text tokens | Yes | Cloudflare’s open decision model. |
cloudflare/clef-flash | 1,800 text tokens | Yes | Lower-cost Clef variant. |
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"], a supported_endpoints list, and a decision_input object. decision_input.image_input is true for the image-capable models, and decision_input.image_limits lists their limits.
For Clef and Clef Flash, the 1,800-token limit covers all text in the request, including state text, questions and request overhead. Images are counted separately; see Image input.
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",
"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,
"estimated_cost": 0.000005292
}
}
noul is the probability of the true outcome. A score can be fractional because it is the expected value across the returned score distribution.
Usage and cost
Every response reportsusage.input_tokens and usage.output_tokens. Only input tokens are billed; output tokens are free.
By default, the response is returned as soon as the answers are ready, and billing settles immediately afterwards. The charge itself is unaffected. These responses report usage.estimated_cost: the model’s published input price for the reported input tokens, in USD. It does not include account discounts or payment-currency conversion, so your actual charge can differ.
To get the exact amount charged instead, send "include_usage": true. The response then waits for billing to settle and reports the exact charge, in USD, as usage.cost instead of usage.estimated_cost. This adds a little latency. Your usage history always shows the exact charge.
"usage": {
"input_tokens": 126,
"output_tokens": 8,
"cost": 0.000005027
}
Image input
perplexity/pplx-decider-v1.1-27b, cloudflare/clef and cloudflare/clef-flash accept images on the native POST /api/v1/decisions endpoint and the POST /api/v1/systemone compatibility endpoint. Use them to classify photos, score visual content, or answer yes/no questions about an image.
Make state an array, and put each image directly in it as an image_url part with a base64 data URL. Plain strings in the same array add text context. The optional image_url.detail field accepts auto, low or high.
IMAGE=$(base64 < return-photo.jpg | tr -d '\n')
curl https://nano-gpt.com/api/v1/decisions \
-H "Authorization: Bearer $NANOGPT_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"model": "perplexity/pplx-decider-v1.1-27b",
"state": [
"Return claim photo.",
{ "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,$IMAGE" } }
],
"questions": {
"condition": {
"type": "choice",
"instructions": "What condition is the returned item in?",
"criteria": {
"like_new": "No visible wear",
"used": "Normal signs of use",
"damaged": "Cracked, broken or missing parts"
}
},
"screen_cracked": {
"type": "noul",
"instructions": "Is the screen cracked?"
}
}
}
EOF
import { readFile } from "node:fs/promises";
const image = (await readFile("return-photo.jpg")).toString("base64");
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: "perplexity/pplx-decider-v1.1-27b",
state: [
"Return claim photo.",
{ type: "image_url", image_url: { url: `data:image/jpeg;base64,${image}` } },
],
questions: {
condition: {
type: "choice",
instructions: "What condition is the returned item in?",
criteria: {
like_new: "No visible wear",
used: "Normal signs of use",
damaged: "Cracked, broken or missing parts",
},
},
screen_cracked: {
type: "noul",
instructions: "Is the screen cracked?",
},
},
}),
});
if (!response.ok) throw new Error(await response.text());
console.log((await response.json()).answers);
import base64
import os
import requests
with open("return-photo.jpg", "rb") as f:
image = base64.b64encode(f.read()).decode()
response = requests.post(
"https://nano-gpt.com/api/v1/decisions",
headers={"Authorization": f"Bearer {os.environ['NANOGPT_API_KEY']}"},
json={
"model": "perplexity/pplx-decider-v1.1-27b",
"state": [
"Return claim photo.",
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image}"}},
],
"questions": {
"condition": {
"type": "choice",
"instructions": "What condition is the returned item in?",
"criteria": {
"like_new": "No visible wear",
"used": "Normal signs of use",
"damaged": "Cracked, broken or missing parts",
},
},
"screen_cracked": {
"type": "noul",
"instructions": "Is the screen cracked?",
},
},
},
timeout=30,
)
response.raise_for_status()
print(response.json()["answers"])
["Photo 1", image1, "Photo 2", image2], and refer to the labels in your question criteria.
| Limit | Value |
|---|---|
| Images per request | Up to 4 |
| Formats | Static PNG, JPEG or WebP, sent inline as base64 data URLs. Remote image URLs are not fetched, and animated images are rejected. |
| Dimensions | Each image at most 512 × 512 pixels |
| Size | All images together at most 240,000 base64 characters (180,000 decoded bytes); the whole JSON body at most 1 MiB |
| Questions | Up to 128 per request |
| Text | Clef and Clef Flash: at most 1,800 text tokens, including questions and request overhead |
usage.input_tokens includes them. Resize larger photos before sending them.
Privacy rules for image requests:
- PPLX Decider v1.1 supports zero data retention: set
provider.zdrtotrue, or use an API key with a ZDR policy. - Clef and Clef Flash do not support zero data retention. On these models, requests that require ZDR, and image requests with
provider.data_collection: "deny", return400. - Image input cannot be combined with PII redaction.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_image | The image is not a valid static PNG, JPEG or WebP data URL, or it is not placed directly in the state array. |
| 400 | unsupported_modality | The model does not accept images, or the requested privacy settings are not available for image input. |
| 413 | image_too_large | An image, or all images together, exceed the limits above. A JSON body over 1 MiB returns 413 request_body_too_large. |
| 400 | context_length_exceeded | The text and images exceed the model’s input limit. |
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, PPLX Decider v1.1, Clef, Clef Flash |
| A request can contain at most 128 questions. | PPLX Decider v1.1, Clef, Clef Flash |
| Text input, including questions and request overhead, is limited to 1,800 tokens. | Clef, Clef Flash |
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. Image-capable models also accept image parts in a state array; see Image input. |
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. |
include_usage | boolean | No | Wait for billing to settle and report the exact charge as usage.cost. Defaults to false, which returns usage.estimated_cost without waiting. See Usage and cost. |
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, PPLX Decider v1.1, Clef and Clef Flash use fixed decision endpoints and accept only zdr and data_collection. Clef and Clef Flash reject zdr: true.
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, perplexity/pplx-decider-v1.1-27b, clef or cloudflare/clef-flash. Image input and include_usage work the same way as on the native endpoint.
// 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)
/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 and Responses reportusage.estimated_cost by default. To get the exact charge as usage.cost, send "include_usage": true or "stream_options": {"include_usage": true}. Messages responses always report the exact charge.
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. Image input is available only on the native Decisions and System One endpoints, for PPLX Decider v1.1, Clef and Clef Flash. - System, developer, assistant, tool, audio, video, and file input is not supported. Compatibility endpoints also reject images.
- 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
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Decision model ID. Bare Decider IDs and clef are aliases of the prefixed IDs.
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, cloudflare/clef-flash, perplexity/pplx-decider-v1.1-27b Application state to evaluate. On https://nano-gpt.com, PPLX Decider v1.1, Clef and Clef Flash also accept an array that mixes plain strings with up to four image_url parts holding static PNG, JPEG or WebP base64 data URLs, each at most 512x512 pixels (240,000 base64 characters in total). Remote image URLs are not fetched.
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, PPLX Decider v1.1, Clef and Clef Flash accept only zdr and data_collection; Clef and Clef Flash reject zdr true.
256256Wait for billing to settle and report the exact charge as usage.cost. By default the response returns without waiting and reports usage.estimated_cost. Available on https://nano-gpt.com.