POST /v1/systemone
The Jev-compatible endpoint. Ask up to 32 typed questions about the same state in one request. The request, response and error formats match TypeSafe AI's Jev System One API, so the official Jev SDKs work unchanged.
To send many requests in one call, use POST /v1/systemone/batch.
POST https://layahost.com/v1/systemone
Coming from Jev? Start with Migrate from Jev. For a single question with a flat response, POST /v1/decide is simpler.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
state | string, object or array | Yes | The text or structured data to evaluate. Objects and arrays are sent to the model as JSON. Up to 32,000 characters. |
model | string | Yes | laya-auto, laya-english or laya-multilingual. Any jev-* name, such as jev-latest, is answered by laya-auto. The SDKs send jev-latest unless you set a model. See Models. |
questions | object | Yes | 1 to 32 questions, keyed by names you choose. Each answer comes back under the same name. |
lang | string | No | layahost extension. A language hint such as en or de. With laya-auto, en sends the state to the English checkpoint and any other value to the multilingual one, instead of detecting the language. Ignored by other models. |
cache | boolean | No | layahost extension. Default true. Set false to skip the response cache. |
Questions
Every question has a type, the question itself in instructions, and for most types a set of possible answers in criteria. Instructions and criteria can be plain strings or JSON objects and arrays. instructions is optional, as in Jev, but a short question tells the model what you are asking, so write one.
type | criteria | Answer |
|---|---|---|
noul |
Optional. {"true": "...", "false": "..."} describing what counts as yes and as no. |
Yes/no. noul is the probability of yes. |
choice |
Required. An object that maps each label to a description, or to null. At least 2 labels, within the option budget. |
One of the labels, with a probability for each. |
score |
Required. An array of levels, lowest first. 2 to 10 levels. | The expected level, with a probability for each level. |
Example
Three questions about one customer message: a choice, a noul and a score.
curl https://layahost.com/v1/systemone \
-H "Authorization: Bearer $LAYAHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Hi, I ordered a blue jacket two weeks ago and it still has not arrived. This is the second time this happens. If it is not here by Friday I want a full refund.",
"model": "laya-auto",
"questions": {
"intent": {
"type": "choice",
"instructions": "What does the customer want?",
"criteria": {
"refund": "wants money back",
"delivery_status": "asks where the order is",
"product_question": "asks about a product",
"other": null
}
},
"is_complaint": {
"type": "noul",
"instructions": "Is the customer complaining?"
},
"anger": {
"type": "score",
"instructions": "How angry is the customer?",
"criteria": ["calm", "annoyed", "angry", "furious"]
}
}
}'
// npm install @typesafe-ai/sdk
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient({
baseURL: "https://layahost.com",
apiKey: process.env.LAYAHOST_API_KEY,
});
const result = await client.systemOne({
model: "laya-auto",
state:
"Hi, I ordered a blue jacket two weeks ago and it still has not arrived. This is the second time this happens. If it is not here by Friday I want a full refund.",
questions: {
intent: choice("What does the customer want?", {
refund: "wants money back",
delivery_status: "asks where the order is",
product_question: "asks about a product",
other: null,
}),
is_complaint: noul("Is the customer complaining?"),
anger: score("How angry is the customer?", ["calm", "annoyed", "angry", "furious"]),
},
});
console.log(result.answers.intent.choice); // "refund"
console.log(result.answers.is_complaint.noul); // probability of yes
console.log(result.answers.anger.score); // 0 = calm ... 3 = furious
# pip install typesafe-sdk
import os
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
with TypeSafeClient(
base_url="https://layahost.com",
api_key=os.environ["LAYAHOST_API_KEY"],
) as client:
result = client.system_one(
model="laya-auto",
state=(
"Hi, I ordered a blue jacket two weeks ago and it still has not arrived. "
"This is the second time this happens. If it is not here by Friday I want a full refund."
),
questions={
"intent": Choice(
instructions="What does the customer want?",
criteria={
"refund": "wants money back",
"delivery_status": "asks where the order is",
"product_question": "asks about a product",
"other": None,
},
),
"is_complaint": Noul(instructions="Is the customer complaining?"),
"anger": Score(
instructions="How angry is the customer?",
criteria=["calm", "annoyed", "angry", "furious"],
),
},
)
print(result.choices["intent"].choice) # "refund"
print(result.nouls["is_complaint"].noul) # probability of yes
print(result.scores["anger"].score) # 0 = calm ... 3 = furious
$client = new GuzzleHttp\Client(['base_uri' => 'https://layahost.com']);
$res = $client->post('/v1/systemone', [
'headers' => ['Authorization' => 'Bearer ' . getenv('LAYAHOST_API_KEY')],
'json' => [
'model' => 'laya-auto',
'state' => 'Hi, I ordered a blue jacket two weeks ago and it still has not arrived. '
. 'This is the second time this happens. If it is not here by Friday I want a full refund.',
'questions' => [
'intent' => [
'type' => 'choice',
'instructions' => 'What does the customer want?',
'criteria' => [
'refund' => 'wants money back',
'delivery_status' => 'asks where the order is',
'product_question' => 'asks about a product',
'other' => null,
],
],
'is_complaint' => ['type' => 'noul', 'instructions' => 'Is the customer complaining?'],
'anger' => [
'type' => 'score',
'instructions' => 'How angry is the customer?',
'criteria' => ['calm', 'annoyed', 'angry', 'furious'],
],
],
],
]);
$result = json_decode($res->getBody(), true);
echo $result['answers']['intent']['choice'], PHP_EOL;
echo $result['answers']['is_complaint']['noul'], PHP_EOL;
echo $result['answers']['anger']['score'], PHP_EOL;
{
"model": "laya-english",
"answers": {
"intent": {
"type": "choice",
"choice": "refund",
"probabilities": {
"refund": 0.8808,
"delivery_status": 0.0788,
"product_question": 0.0268,
"other": 0.0136
},
"confidence": 0.6627
},
"is_complaint": {
"type": "noul",
"noul": 0.7899,
"confidence": 0.7899
},
"anger": {
"type": "score",
"score": 1.5154,
"legend": {
"0": "calm",
"1": "annoyed",
"2": "angry",
"3": "furious"
},
"probabilities": {
"0": 0.0229,
"1": 0.5684,
"2": 0.279,
"3": 0.1297
},
"confidence": 0.2579
}
},
"usage": {
"input_tokens": 218,
"output_tokens": 0
},
"meta": {
"checkpoint": "english",
"cached": false,
"latency_ms": 307.75,
"request_id": "req_wxxjgdeytykryrijxucxwmq3",
"decisions": 3,
"cost_micros": 15,
"cost_usd": 1.5e-5
}
}
Response
| Field | Type | Description |
|---|---|---|
model | string | The model that answered: laya-english or laya-multilingual. With laya-auto or a jev-* name, this is the checkpoint it chose. |
answers | object | One answer per question, under the question's name. Fields depend on the type (below). |
usage.input_tokens | integer | Tokens the model read, over all questions. |
usage.output_tokens | integer | Always 0. Laya generates no text. |
meta | object | layahost extension, ignored by the Jev SDKs. See meta. |
Answer fields
| Type | Field | Description |
|---|---|---|
choice | choice | The most likely label. |
probabilities | Probability per label, in the order you sent them. | |
confidence | 1 minus the normalised entropy of probabilities: 0 when all labels are equally likely, 1 when one label has all of the probability. | |
noul | noul | Probability that the answer is yes, from 0 to 1. |
confidence | layahost extension: the larger of noul and 1 − noul. | |
score | score | The expected level: each level's index (0 for the first) times its probability, summed. Ranges from 0 to n−1 and can fall between levels. |
legend | Your levels keyed by index: "0" is the first. | |
probabilities | Probability per level, keyed by index. | |
confidence | Same measure as for choice. |
meta
| Field | Type | Description |
|---|---|---|
checkpoint | string | english or multilingual. |
cached | boolean | Whether the answers came from the response cache. |
latency_ms | number | Server-side processing time in milliseconds. |
request_id | string | Unique ID of the request, also in the x-request-id and x-typesafe-request-id headers and in Logs. |
decisions | integer | Decisions billed: one per question. |
cost_micros | integer | What the request cost, in millionths of a US dollar: 15 is $0.000015. Use this for exact accounting. |
cost_usd | number | The same cost in US dollars, as a JSON number that may be written in exponent form: 1.5e-5 is $0.000015. |
Structured state
state can be a JSON object or array, for example a ticket with its metadata. The model sees it as JSON text:
curl https://layahost.com/v1/systemone \
-H "Authorization: Bearer $LAYAHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": {
"subject": "Refund",
"body": "Please refund my last order.",
"customer_tier": "gold"
},
"model": "laya-english",
"cache": false,
"questions": {
"refund": {
"type": "noul",
"instructions": "Does the customer ask for a refund?"
}
}
}'
{
"model": "laya-english",
"answers": {
"refund": {
"type": "noul",
"noul": 0.7831,
"confidence": 0.7831
}
},
"usage": {
"input_tokens": 59,
"output_tokens": 0
},
"meta": {
"checkpoint": "english",
"cached": false,
"latency_ms": 96.18,
"request_id": "req_zpkrvcyzsmdkqqpsuxy1dte6",
"decisions": 1,
"cost_micros": 5,
"cost_usd": 5.0e-6
}
}
Language hint
With laya-auto, each request goes to the English checkpoint only when the state is detected as English, and to the multilingual checkpoint otherwise. If you already know the language, pass lang to skip detection:
curl https://layahost.com/v1/systemone \
-H "Authorization: Bearer $LAYAHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Ich möchte mein Abo kündigen.",
"model": "laya-auto",
"lang": "de",
"cache": false,
"questions": {
"cancel": {
"type": "noul",
"instructions": "Does the customer want to cancel?"
}
}
}'
{
"model": "laya-multilingual",
"answers": {
"cancel": {
"type": "noul",
"noul": 0.9885,
"confidence": 0.9885
}
},
"usage": {
"input_tokens": 40,
"output_tokens": 0
},
"meta": {
"checkpoint": "multilingual",
"cached": false,
"latency_ms": 31.08,
"request_id": "req_5saxpesxm6qwhrssxrha8tjv",
"decisions": 1,
"cost_micros": 5,
"cost_usd": 5.0e-6
}
}
Caching
Identical requests (same checkpoint, state and questions) may be answered from an in-memory cache for up to an hour. A cached response has the same answers, meta.cached set to true and a very low latency_ms. Repeating the three-question example above returns:
{
"checkpoint": "english",
"cached": true,
"latency_ms": 0.37,
"request_id": "req_cvwvpdksqcrakbv1cuzmr0mg",
"decisions": 3,
"cost_micros": 15,
"cost_usd": 1.5e-5
}
Cached answers are billed like any other. Laya gives the same answer for the same input either way, so the cache only saves time. Set "cache": false to skip the cache completely: the answer is neither read from it nor stored in it. That is useful when you measure latency.
Billing
Each question is one decision, at $0.000005. The example above has three questions, so it costs $0.000015, as meta.decisions and meta.cost_micros show. Requests that fail are not billed. See Pricing & billing.
Limits
| What | Limit |
|---|---|
| Questions per request | 1 to 32 |
state | 32,000 characters (objects and arrays count as JSON text) |
choice labels | At least 2. Jev allows up to 255, but on layahost all labels and descriptions of one question must fit in the model's option budget of 192 tokens, roughly 100 to 150 short labels. Longer lists get 422. |
score levels | 2 to 10 |
Errors
Invalid requests get 422 in the FastAPI format that Jev uses, and are not billed. An unknown model:
{
"detail": [
{
"loc": ["body", "model"],
"msg": "Model 'gpt-4o' is not available. Use GET /v1/models.",
"type": "value_error"
}
]
}
Errors in a question's shape point to the question by name, and include the offending input and a ctx object. Here the choice question q had a single label:
{
"detail": [
{
"type": "too_short",
"loc": ["body", "questions", "q", "choice", "criteria"],
"msg": "Dictionary should have at least 2 items after validation, not 1",
"input": {"yes": null},
"ctx": {
"field_type": "Dictionary",
"min_length": 2,
"actual_length": 1
}
}
]
}
A question whose labels and descriptions don't fit the option budget:
{
"detail": [
{
"loc": ["body", "questions", "category", "criteria"],
"msg": "The options of this question are too long for the model: all option names and descriptions together must fit in 192 tokens (roughly 100-150 short options). Shorten descriptions or split the question.",
"type": "value_error"
}
]
}
Too many questions:
{
"detail": [
{
"loc": ["body", "questions"],
"msg": "At most 32 questions per request.",
"type": "value_error"
}
]
}
See Errors for authentication, balance, rate-limit and capacity errors.