> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Decision Models (Beta)

> Use the beta API to route support requests, evaluate yes/no conditions, and rate urgency with the TypeSafe-compatible Telnyx decision model API.

<Note>
  Decision Models is in **beta**. Telnyx manages model selection; there is no
  customer-selectable model setting.
</Note>

`POST https://api.telnyx.com/v2/ai/typesafe/v1/systemone` evaluates shared context against
named questions and returns structured answers. Use `choice` to select a
category, `noul` to evaluate a yes/no condition, and `score` to rate an ordered
rubric. A request can combine all three question types.

The endpoint supports a subset of the
[TypeSafe System One API](https://docs.typesafe.ai/concepts/system-one)
request format and preserves its typed answer shapes. It returns one complete
JSON response. See the [API reference](/api-reference/decision-models/evaluate-decision-models-typesafe-compatible)
for the full request and response schemas.

## Classify a support incident

Set `TELNYX_API_KEY` to a Telnyx API key. Send the incident as `state`, then
use named questions to select the team, identify a production incident, and
rate urgency in one request.

```bash theme={null}
curl --fail-with-body --max-time 100 \
  'https://api.telnyx.com/v2/ai/typesafe/v1/systemone' \
  -H "Authorization: Bearer ${TELNYX_API_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{
    "state": "Our production calls are failing. Every customer is affected.",
    "questions": {
      "team": {
        "type": "choice",
        "instructions": "Choose the team that should handle this incident.",
        "criteria": {
          "billing": "Payments and refunds",
          "technical_support": "Service faults and technical problems",
          "sales": "New purchases"
        }
      },
      "production_incident": {
        "type": "noul",
        "instructions": "Does the message describe an active production incident?"
      },
      "urgency": {
        "type": "score",
        "instructions": "Rate operational urgency.",
        "criteria": ["Low", "Normal", "High", "Critical"]
      }
    }
  }'
```

The response contains `model`, `answers`, and `usage` directly, with no `data`
wrapper. The `model` value is an opaque Telnyx-controlled compatibility
identifier, not a selectable model name. Each key in `answers` matches a key in
`questions`. The following example rounds values for readability; results and token counts can vary.

```json theme={null}
{
  "model": "telnyx-managed",
  "answers": {
    "team": {
      "type": "choice",
      "choice": "technical_support",
      "probabilities": {
        "billing": 0.002472,
        "technical_support": 0.997267,
        "sales": 0.000261
      },
      "confidence": 0.982052
    },
    "production_incident": {
      "type": "noul",
      "noul": 0.999196
    },
    "urgency": {
      "type": "score",
      "score": 2.997424,
      "legend": {"0": "Low", "1": "Normal", "2": "High", "3": "Critical"},
      "probabilities": {"0": 0.000335, "1": 0.000035, "2": 0.001501, "3": 0.998129},
      "confidence": 0.989420
    }
  },
  "usage": {"input_tokens": 267, "output_tokens": 4}
}
```

Read `answers.team.choice` to choose a destination. Interpret
`answers.production_incident.noul` as a numeric yes-score, and
`answers.urgency.score` on the requested 0–3 rubric. Token usage includes
shared-context preparation and question evaluation, so it can exceed the
size of the unique input text.

## Choose question types

Every question requires `type` and `instructions`. Instructions and `state`
can be strings, JSON objects, or arrays. Text-only conversation histories
are supported; image and audio inputs are not supported.

| Type                                                 | Criteria                                                                                                                                                                                                                                         | Answer                                                                                                                                                                                                                                                       |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| <code style={{ whiteSpace: "nowrap" }}>choice</code> | Object with 2–64 option keys. Values are strings or <code style={{ whiteSpace: "nowrap" }}>null</code>; <code style={{ whiteSpace: "nowrap" }}>null</code> uses the key as the option description.                                               | <code style={{ whiteSpace: "nowrap" }}>choice</code> contains the winning key, <code style={{ whiteSpace: "nowrap" }}>probabilities</code> contains all option scores, and <code style={{ whiteSpace: "nowrap" }}>confidence</code> describes concentration. |
| <code style={{ whiteSpace: "nowrap" }}>noul</code>   | Optional object with string descriptions for <code style={{ whiteSpace: "nowrap" }}>true</code> and <code style={{ whiteSpace: "nowrap" }}>false</code>. Defaults to <code style={{ whiteSpace: "nowrap" }}>\{"true":"Yes","false":"No"}</code>. | <code style={{ whiteSpace: "nowrap" }}>noul</code> is the positive outcome's score from 0 to 1. There is no separate confidence field.                                                                                                                       |
| <code style={{ whiteSpace: "nowrap" }}>score</code>  | Ordered array of 2–64 description strings.                                                                                                                                                                                                       | <code style={{ whiteSpace: "nowrap" }}>score</code> is the expected zero-based index; <code style={{ whiteSpace: "nowrap" }}>legend</code> and <code style={{ whiteSpace: "nowrap" }}>probabilities</code> use stringified indices.                          |

A `choice` question selects one option. Use separate `noul` questions when
several independent conditions can be true at once. For example, a support
message can both request a refund and report a service fault.

A `score` answer can be fractional. For criteria `["Low", "Normal", "High",
"Critical"]`, the range is 0–3. Compute the expected score as
`sum(index * probability)`; do not treat it as a 0–1 probability or an array
index without an application-specific decision rule.

## Use scores in application logic

Option scores are normalized relative preferences across the supplied
choices. Changing the choices or their wording can change the distribution.
They are not calibrated probabilities that a decision is correct.

For `choice` and `score`, `confidence` is normalized entropy:
`1 - H(p) / ln(N)`, where `H(p) = -sum(p * ln(p))` and `N` is the number of
options. It approaches 0 for a uniform distribution and 1 when the score is
concentrated on one option. It is different from the winning option's
probability.

Choose review thresholds using representative examples from the application.
The following Python example uses direct HTTP, selects a team, and falls back
to manual review when the winning option has a low relative score. The `0.8`
threshold is illustrative and must be evaluated for the application's data.

```python theme={null}
import json
import os
from urllib.error import HTTPError
from urllib.request import Request, urlopen

payload = {
    "state": "The invoice looks right, but the usage page counts calls twice.",
    "questions": {
        "team": {
            "type": "choice",
            "instructions": "Choose the team for the underlying issue.",
            "criteria": {
                "billing": "Incorrect charges or refunds",
                "technical_support": "Service faults or software defects",
                "sales": "New purchases",
            },
        }
    },
}
request = Request(
    "https://api.telnyx.com/v2/ai/typesafe/v1/systemone",
    data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {os.environ['TELNYX_API_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
try:
    with urlopen(request, timeout=100) as response:
        result = json.load(response)
except HTTPError as error:
    raise RuntimeError(f"Decision model failed with HTTP {error.code}") from error

answer = result["answers"]["team"]
selected = answer["choice"]
winning_score = answer["probabilities"][selected]
destination = selected if winning_score >= 0.8 else "manual_review"
print(destination)
```

## Migrate a TypeSafe request

Point HTTP requests to `https://api.telnyx.com/v2/ai/typesafe/v1/systemone` and authenticate
with a Telnyx Bearer API key. Send `state` and `questions`, keeping the question
IDs used by the application. Telnyx chooses the underlying model.

Compatibility applies to the supported JSON request subset and typed answer
shapes. It does not imply identical model predictions, confidence calibration,
pricing, or token accounting.

| Area                       | Telnyx behavior                                                                                                                              |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Route                      | `POST /v2/ai/typesafe/v1/systemone`.                                                                                                         |
| Model selection            | Managed by Telnyx. SDK-supplied model values are ignored. The response retains an opaque Telnyx-controlled identifier for SDK compatibility. |
| Question instructions      | Required for every question. TypeSafe also accepts omitted or null instructions; this subset does not.                                       |
| Criterion descriptions     | Strings for all question types; `choice` also accepts `null`. TypeSafe's object and array descriptions are not supported here.               |
| Question and option limits | 1–64 questions; 2–64 options for each `choice` or `score`. TypeSafe's one-level score rubric is not supported.                               |
| Answers                    | Named `choice`, `noul`, and `score` answers, with `model` and token `usage` at the top level.                                                |
| SDK base URL               | Set `base_url="https://api.telnyx.com/v2/ai/typesafe"`. The SDK appends `/v1/systemone`; no route adapter is required.                       |

The [official TypeSafe Python SDK](https://github.com/typesafe-ai/typesafe-sdk-python)
appends `/v1/systemone` to the configured base URL. The route preserves that
behavior. Its `client.models.list()` method uses a separate TypeSafe route and
is outside this endpoint's compatibility scope. There is no public model-selection
setting. The SDK automatically sends its model value; Telnyx ignores it, so
existing client defaults and overrides cannot choose the underlying model.

## Use the TypeSafe Python SDK

Install the [official SDK](https://github.com/typesafe-ai/typesafe-sdk-python):

```bash theme={null}
pip install typesafe-sdk
```

Set the Telnyx base URL and API key. Existing `system_one()`
calls using the supported question subset keep the same method and answer
accessors. This example uses the SDK's `Choice`, `Noul`, and `Score` types.

```python theme={null}
import os
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

with TypeSafeClient(
    api_key=os.environ["TELNYX_API_KEY"],
    base_url="https://api.telnyx.com/v2/ai/typesafe",
    timeout=100,
) as client:
    result = client.system_one(
        state="Our production calls are failing. Every customer is affected.",
        questions={
            "team": Choice(
                instructions="Choose the team that should handle this incident.",
                criteria={
                    "billing": "Payments and refunds",
                    "technical_support": "Service faults and technical problems",
                    "sales": "New purchases",
                },
            ),
            "production_incident": Noul(
                instructions="Does the message describe an active production incident?",
            ),
            "urgency": Score(
                instructions="Rate operational urgency.",
                criteria=["Low", "Normal", "High", "Critical"],
            ),
        },
    )
    print(result.choices["team"].choice)
    print(result.nouls["production_incident"].noul)
    print(result.scores["urgency"].score)
```

The SDK sends a Bearer authorization header from `api_key`. Use a Telnyx key
for Telnyx requests. Keep `/v1/systemone` out of `base_url`; the SDK adds it.

## Limits and failures

Use up to 64 named questions against one shared `state`. Split larger workloads
into separate requests and bound client concurrency. Responses arrive after
the complete evaluation; there is no streaming or per-question partial-success
envelope. Request-body and token limits also apply, so question count alone
does not guarantee that a request fits.

The public request fields are `state` and `questions`. The SDK-supplied `model`
value is ignored for compatibility. Other unknown fields are rejected.
Do not send `input`, `labels`, `tier`, `stream`,
`temperature`, or `max_tokens` to this endpoint.

| Status       | Action                                                                    |
| ------------ | ------------------------------------------------------------------------- |
| `401`        | Check the Telnyx API key.                                                 |
| `413`        | Reduce or fix the request body.                                           |
| `422`        | Correct the schema or split context that exceeds token limits.            |
| `429`, `529` | Reduce concurrency and retry with bounded exponential backoff and jitter. |
| `502`, `503` | Retry transient service failures with bounded backoff.                    |
| `504`        | Reduce request size or concurrency before retrying.                       |

Check the HTTP status before parsing an answer. Honor `Retry-After` when
present. Correct validation failures before retrying; retries repeat evaluation
work. A decision model service error uses this shape, with a message describing
the failure:

```json theme={null}
{
  "error": {
    "message": "Each question requires text/JSON instructions and 2–64 options"
  }
}
```
