# 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](https://layahost.com/docs/batch).

Endpoint

```http
POST https://layahost.com/v1/systemone
```

Coming from Jev? Start with [Migrate from Jev](https://layahost.com/docs/migrate-from-jev). For a single question with a flat response, [POST /v1/decide](https://layahost.com/docs/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](https://layahost.com/docs/models). |
| `questions` | object | Yes | 1 to 32 [questions](#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](#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](#limits). | 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`.

POST /v1/systemone

```bash
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"]
      }
    }
  }'
```

POST /v1/systemone

```javascript
// 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
```

POST /v1/systemone

```python
# 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
```

POST /v1/systemone

```php
$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;
```

Response · 200

```json
{
  "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](#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](https://layahost.com/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:

POST /v1/systemone

```bash
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?"
      }
    }
  }'
```

Response · 200

```json
{
  "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:

POST /v1/systemone

```bash
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?"
      }
    }
  }'
```

Response · 200

```json
{
  "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:

meta · cached response

```json
{
  "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](https://layahost.com/docs/pricing).

## 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:

Response · 422

```json
{
  "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:

Response · 422

```json
{
  "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:

Response · 422

```json
{
  "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:

Response · 422

```json
{
  "detail": [
    {
      "loc": ["body", "questions"],
      "msg": "At most 32 questions per request.",
      "type": "value_error"
    }
  ]
}
```

See [Errors](https://layahost.com/docs/errors) for authentication, balance, rate-limit and capacity errors.
