Decisions
curl --request POST \
--url https://api.scaledown.xyz/v1/scaledown \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"state": {
"text": "<string>",
"document": "<string>",
"document_mime_type": "<string>"
},
"questions": {},
"model": "classify-1"
}
'import requests
url = "https://api.scaledown.xyz/v1/scaledown"
payload = {
"state": {
"text": "<string>",
"document": "<string>",
"document_mime_type": "<string>"
},
"questions": {},
"model": "classify-1"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
state: {text: '<string>', document: '<string>', document_mime_type: '<string>'},
questions: {},
model: 'classify-1'
})
};
fetch('https://api.scaledown.xyz/v1/scaledown', 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.scaledown.xyz/v1/scaledown",
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' => [
'text' => '<string>',
'document' => '<string>',
'document_mime_type' => '<string>'
],
'questions' => [
],
'model' => 'classify-1'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$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.scaledown.xyz/v1/scaledown"
payload := strings.NewReader("{\n \"state\": {\n \"text\": \"<string>\",\n \"document\": \"<string>\",\n \"document_mime_type\": \"<string>\"\n },\n \"questions\": {},\n \"model\": \"classify-1\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
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.scaledown.xyz/v1/scaledown")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"state\": {\n \"text\": \"<string>\",\n \"document\": \"<string>\",\n \"document_mime_type\": \"<string>\"\n },\n \"questions\": {},\n \"model\": \"classify-1\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scaledown.xyz/v1/scaledown")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"state\": {\n \"text\": \"<string>\",\n \"document\": \"<string>\",\n \"document_mime_type\": \"<string>\"\n },\n \"questions\": {},\n \"model\": \"classify-1\"\n}"
response = http.request(request)
puts response.read_body{
"model": "<string>",
"answers": {},
"usage": {
"input_tokens": 123,
"output_tokens": 123,
"cost": 123
}
}Decisions
Decisions
Jev (TypeSafe)-compatible endpoint: answer typed choice/noul/score questions against a shared state object.
POST
/
v1
/
scaledown
Decisions
curl --request POST \
--url https://api.scaledown.xyz/v1/scaledown \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"state": {
"text": "<string>",
"document": "<string>",
"document_mime_type": "<string>"
},
"questions": {},
"model": "classify-1"
}
'import requests
url = "https://api.scaledown.xyz/v1/scaledown"
payload = {
"state": {
"text": "<string>",
"document": "<string>",
"document_mime_type": "<string>"
},
"questions": {},
"model": "classify-1"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
state: {text: '<string>', document: '<string>', document_mime_type: '<string>'},
questions: {},
model: 'classify-1'
})
};
fetch('https://api.scaledown.xyz/v1/scaledown', 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.scaledown.xyz/v1/scaledown",
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' => [
'text' => '<string>',
'document' => '<string>',
'document_mime_type' => '<string>'
],
'questions' => [
],
'model' => 'classify-1'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$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.scaledown.xyz/v1/scaledown"
payload := strings.NewReader("{\n \"state\": {\n \"text\": \"<string>\",\n \"document\": \"<string>\",\n \"document_mime_type\": \"<string>\"\n },\n \"questions\": {},\n \"model\": \"classify-1\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
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.scaledown.xyz/v1/scaledown")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"state\": {\n \"text\": \"<string>\",\n \"document\": \"<string>\",\n \"document_mime_type\": \"<string>\"\n },\n \"questions\": {},\n \"model\": \"classify-1\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scaledown.xyz/v1/scaledown")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"state\": {\n \"text\": \"<string>\",\n \"document\": \"<string>\",\n \"document_mime_type\": \"<string>\"\n },\n \"questions\": {},\n \"model\": \"classify-1\"\n}"
response = http.request(request)
puts response.read_body{
"model": "<string>",
"answers": {},
"usage": {
"input_tokens": 123,
"output_tokens": 123,
"cost": 123
}
}Overview
The/v1/scaledown endpoint mirrors TypeSafe’s Jev Decisions API request/response shape. You send a state object plus a questions map, and get back one typed answer per question. choice, noul, and score questions are all answered using the same calibrated scoring /classify uses.
Request
string
default:"classify-1"
Optional. The only supported value is
"classify-1", which is also the default when omitted. Any other value is rejected with 422.object
required
The item being decided on.
Show State fields
Show State fields
string
The text to evaluate every question against. Provide
text, document, or both.string
A base64-encoded file to evaluate. Supported formats: JPEG, PNG, TIFF, single-page PDF, multi-page PDF. Non-standard extension - not part of the Jev API.
string
MIME type of
document (e.g. "image/jpeg", "application/pdf"). Required when document is provided.object
required
A map of question name → question definition. Must contain at least one entry. Every question is answered against the same
state, concurrently.Show choice question
Show choice question
Show noul question
Show noul question
Show score question
Show score question
string
required
"score" - an ordered-scale question, answered as a position on the scale.string
Optional guidance for how to score (e.g. “How severe is the reported issue?”).
array
required
An ordered list of 2 to 10 level descriptions, from lowest to highest, e.g.
["Cosmetic; no impact to functionality", "Broken or degraded feature, but workaround exists", "Blocking issue; no workaround exists"]. Levels are indexed by array position starting at 0 - unlike choice’s criteria, this is an array, not a map, because the order is meaningful.Response
string
Always
"classify-1".object
Map of question name → typed answer, matching the
questions keys from the request.Show choice answer
Show choice answer
Show score answer
Show score answer
string
"score"number
Probability-weighted expected value across the levels:
Σ (level_index × P(level)). A continuous position on the scale, e.g. 1.43 for a 3-level (0, 1, 2) scale - not necessarily a whole level.number
The most likely level’s probability mass (same value as
max(probabilities.values())).object
Map of level index (as a string, e.g.
"0") → its description, echoing criteria back for display.object
Map of level index (as a string) → probability. Sums to
1.0.object
How score is calculated
A score question is answered with the same constrained, calibrated decode choice uses - one model call scores every level in criteria, producing a probability distribution over them (probabilities). That distribution is then reduced to a single continuous score value: the probability-weighted expected value across level indices.
For example, given criteria with 3 levels (indices 0, 1, 2) and probabilities: {"0": 0.0, "1": 0.57, "2": 0.43}:
score = 0×0.0 + 1×0.57 + 2×0.43 = 1.43
score is therefore a continuous position on the scale, not necessarily a whole level - a value near 1.43 says the model leans toward level 2 but isn’t fully decided between levels 1 and 2. Use probabilities (or confidence, the top level’s own probability) if you need the calibration behind that number instead of just the point estimate.
Error responses
| Status | Meaning |
|---|---|
422 Unprocessable Entity | Malformed request body, empty questions map, empty criteria on a choice question, criteria outside the 2-10 range on a score question, neither state.text nor state.document provided, an unsupported model value, or OCR failed. |
402 Payment Required | Insufficient credits. |
502 Bad Gateway | Model service unavailable or returned an error. |
504 Gateway Timeout | Request timed out. |
Authentication
Include your API key in every request using thex-api-key header.
-H "x-api-key: <your-api-key>"
Examples
Single choice question
curl -X POST https://api.scaledown.xyz/v1/scaledown \
-H "Content-Type: application/json" \
-H "x-api-key: <your-api-key>" \
-d '{
"model": "classify-1",
"state": { "text": "I was charged twice for my subscription this month." },
"questions": {
"category": {
"type": "choice",
"instructions": "Which single category best describes the post?",
"criteria": {
"billing": "About a charge, invoice, refund, or payment problem.",
"technical": "About a bug or something not working.",
"account": "About login, access, or account settings."
}
}
}
}'
import requests
response = requests.post(
"https://api.scaledown.xyz/v1/scaledown",
headers={"x-api-key": "<your-api-key>"},
json={
"model": "classify-1",
"state": {"text": "I was charged twice for my subscription this month."},
"questions": {
"category": {
"type": "choice",
"instructions": "Which single category best describes the post?",
"criteria": {
"billing": "About a charge, invoice, refund, or payment problem.",
"technical": "About a bug or something not working.",
"account": "About login, access, or account settings.",
},
}
},
},
)
print(response.json())
const response = await fetch("https://api.scaledown.xyz/v1/scaledown", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "<your-api-key>",
},
body: JSON.stringify({
model: "classify-1",
state: { text: "I was charged twice for my subscription this month." },
questions: {
category: {
type: "choice",
instructions: "Which single category best describes the post?",
criteria: {
billing: "About a charge, invoice, refund, or payment problem.",
technical: "About a bug or something not working.",
account: "About login, access, or account settings.",
},
},
},
}),
});
const data = await response.json();
{
"model": "classify-1",
"answers": {
"category": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.93, "technical": 0.04, "account": 0.03 },
"confidence": 0.93
}
},
"usage": { "input_tokens": 62, "output_tokens": 1, "cost": 0.0000026 }
}
Mixed choice + noul questions
choice and noul questions can be combined in one request. Both are answered concurrently against the same state.
curl -X POST https://api.scaledown.xyz/v1/scaledown \
-H "Content-Type: application/json" \
-H "x-api-key: <your-api-key>" \
-d '{
"state": { "text": "The invoice is overdue and the customer is threatening to cancel." },
"questions": {
"category": {
"type": "choice",
"criteria": {
"billing": "About a charge, invoice, or payment matter.",
"legal": "About a legal or contractual dispute."
}
},
"is_churn_risk": {
"type": "noul",
"instructions": "Does this text signal the customer may cancel or leave?"
}
}
}'
import requests
response = requests.post(
"https://api.scaledown.xyz/v1/scaledown",
headers={"x-api-key": "<your-api-key>"},
json={
"state": {"text": "The invoice is overdue and the customer is threatening to cancel."},
"questions": {
"category": {
"type": "choice",
"criteria": {
"billing": "About a charge, invoice, or payment matter.",
"legal": "About a legal or contractual dispute.",
},
},
"is_churn_risk": {
"type": "noul",
"instructions": "Does this text signal the customer may cancel or leave?",
},
},
},
)
print(response.json())
const response = await fetch("https://api.scaledown.xyz/v1/scaledown", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "<your-api-key>",
},
body: JSON.stringify({
state: { text: "The invoice is overdue and the customer is threatening to cancel." },
questions: {
category: {
type: "choice",
criteria: {
billing: "About a charge, invoice, or payment matter.",
legal: "About a legal or contractual dispute.",
},
},
is_churn_risk: {
type: "noul",
instructions: "Does this text signal the customer may cancel or leave?",
},
},
}),
});
const data = await response.json();
{
"model": "classify-1",
"answers": {
"category": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.88, "legal": 0.12 },
"confidence": 0.88
},
"is_churn_risk": { "type": "noul", "noul": 0.79 }
},
"usage": { "input_tokens": 94, "output_tokens": 2, "cost": 0.0000039 }
}
A score question
Use score for an ordered-scale rating, like severity or quality. criteria is an ordered array, not a map - level 0 is the first item, level 1 the second, and so on.
curl -X POST https://api.scaledown.xyz/v1/scaledown \
-H "Content-Type: application/json" \
-H "x-api-key: <your-api-key>" \
-d '{
"state": { "text": "The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari." },
"questions": {
"bug_severity": {
"type": "score",
"instructions": "How severe is the reported issue?",
"criteria": [
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists"
]
}
}
}'
import requests
response = requests.post(
"https://api.scaledown.xyz/v1/scaledown",
headers={"x-api-key": "<your-api-key>"},
json={
"state": {
"text": "The export button crashes the settings page in Safari. "
"It works in Chrome, but a few of our customers only use Safari."
},
"questions": {
"bug_severity": {
"type": "score",
"instructions": "How severe is the reported issue?",
"criteria": [
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists",
],
}
},
},
)
print(response.json())
const response = await fetch("https://api.scaledown.xyz/v1/scaledown", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "<your-api-key>",
},
body: JSON.stringify({
state: {
text: "The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
},
questions: {
bug_severity: {
type: "score",
instructions: "How severe is the reported issue?",
criteria: [
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists",
],
},
},
}),
});
const data = await response.json();
{
"model": "classify-1",
"answers": {
"bug_severity": {
"type": "score",
"score": 1.43,
"confidence": 0.57,
"legend": {
"0": "Cosmetic; no impact to functionality",
"1": "Broken or degraded feature, but workaround exists",
"2": "Blocking issue; no workaround exists"
},
"probabilities": { "0": 0.0, "1": 0.57, "2": 0.43 }
}
},
"usage": { "input_tokens": 68, "output_tokens": 1, "cost": 0.0000029 }
}
score: 1.43 (0×0.0 + 1×0.57 + 2×0.43) says the model leans toward “blocking issue” (level 2) but isn’t fully decided between that and “broken or degraded” (level 1).
Mixing all three question types
choice, noul, and score questions can all appear in the same request. Every question is answered concurrently against the same state, and usage sums across all of them.
curl -X POST https://api.scaledown.xyz/v1/scaledown \
-H "Content-Type: application/json" \
-H "x-api-key: <your-api-key>" \
-d '{
"state": { "text": "Great product, but the onboarding was confusing." },
"questions": {
"sentiment": {
"type": "choice",
"criteria": { "positive": "Overall positive tone.", "negative": "Overall negative tone." }
},
"is_actionable": {
"type": "noul",
"instructions": "Does this feedback suggest a specific, actionable improvement?"
},
"quality_score": {
"type": "score",
"instructions": "Rate overall product quality.",
"criteria": ["poor", "average", "excellent"]
}
}
}'
{
"model": "classify-1",
"answers": {
"sentiment": {
"type": "choice",
"choice": "positive",
"probabilities": { "positive": 0.81, "negative": 0.19 },
"confidence": 0.81
},
"is_actionable": { "type": "noul", "noul": 0.72 },
"quality_score": {
"type": "score",
"score": 1.65,
"confidence": 0.51,
"legend": { "0": "poor", "1": "average", "2": "excellent" },
"probabilities": { "0": 0.03, "1": 0.46, "2": 0.51 }
}
},
"usage": { "input_tokens": 103, "output_tokens": 3, "cost": 0.0000044 }
}
Notes
"classify-1"is the only supportedmodel. Omit it (defaults to"classify-1") or pass it explicitly - any other value is rejected with422.- Every question in a request -
choice,noul, orscore- is one separate calibrated model call, run concurrently.usageis the sum across all of them - a request with achoice, anoul, and ascorequestion makes 3 calls and bills for all 3. score’scriteriais an ordered array (2-10 levels), unlikechoice’s unordered{key: description}map - the array position is the level index.- No
reasoningfield exists on this endpoint - it never generates a free-form explanation, unlike/classifywithreasoning: true. - No batching - each request evaluates one
state. To classify many items, use/classify’schunksarray instead. - See Differences from Jev for the full compatibility matrix.
Authorizations
Body
application/json
The item being decided on. Provide text, document, or both.
Show child attributes
Show child attributes
Map of question name to question definition. Must contain at least one entry.
Show child attributes
Show child attributes
Optional. "classify-1" is the only supported value and also the default when omitted. Any other value returns 422.
Available options:
classify-1