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

# Decisions

> Turn application state and typed questions into calibrated probabilities with the Decisions API.

<Warning>
  **Beta Feature**

  The Decisions API is currently in beta.
</Warning>

## Overview

`POST /v1/decisions` serves TypeSafe's Jev model, a System One model. Unlike the Responses or Chat Completions APIs, it doesn't generate text. You send it your application's state and a map of typed questions, and it returns a calibrated probability for each one.

Use this when your code needs a number it can branch on, such as "how likely is this support ticket to be a bug report" or "which of these three categories does this transaction belong to," rather than a paragraph it has to parse.

<Info>
  The request and response shapes are identical to TypeSafe's System One API and OpenRouter's Decisions API, so a client written against either works here by pointing it at `/v1/decisions`. Fields this endpoint doesn't implement, including OpenRouter's `routing`, are ignored rather than rejected: Jev is the only model behind the endpoint, so there's nothing to route between.
</Info>

## Request

<ParamField body="model" type="string" required>
  The model to use. See [Model and aliases](#model-and-aliases).
</ParamField>

<ParamField body="state" type="string | object | array" required>
  The application state the questions are judged against. Accepts a plain string, a JSON object, or a JSON array.
</ParamField>

<ParamField body="questions" type="object" required>
  A map of caller-chosen question ids to question objects. Must contain at least one question. Each question has a `type` of `noul`, `choice`, or `score`, plus `instructions` and (depending on type) `criteria`. Both `instructions` and `criteria` accept structured JSON, not only strings: a string, an object, or an array, with any JSON values inside that object or array.
</ParamField>

## Question types

### `noul`

A yes/no judgment. Returns a probability between `0` and `1` that the answer is yes. `criteria` is optional, and may carry `true` and `false` descriptions to sharpen the judgment.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.concentrate.ai/v1/decisions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -d '{
      "model": "jev-1.13.0",
      "state": "The export button does nothing when I click it, and no file downloads.",
      "questions": {
        "is_bug_report": {
          "type": "noul",
          "instructions": "Is this message reporting a bug?",
          "criteria": {
            "true": "The customer describes the product behaving incorrectly",
            "false": "The customer asks a question or makes a request without reporting a defect"
          }
        }
      }
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.concentrate.ai/v1/decisions",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Content-Type": "application/json"
      },
      json={
          "model": "jev-1.13.0",
          "state": "The export button does nothing when I click it, and no file downloads.",
          "questions": {
              "is_bug_report": {
                  "type": "noul",
                  "instructions": "Is this message reporting a bug?",
                  "criteria": {
                      "true": "The customer describes the product behaving incorrectly",
                      "false": "The customer asks a question or makes a request without reporting a defect"
                  }
              }
          }
      }
  )

  print(response.json()["answers"]["is_bug_report"])
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.concentrate.ai/v1/decisions", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "jev-1.13.0",
      state: "The export button does nothing when I click it, and no file downloads.",
      questions: {
        is_bug_report: {
          type: "noul",
          instructions: "Is this message reporting a bug?",
          criteria: {
            true: "The customer describes the product behaving incorrectly",
            false: "The customer asks a question or makes a request without reporting a defect"
          }
        }
      }
    })
  });

  const data = await response.json();
  console.log(data.answers.is_bug_report);
  ```
</CodeGroup>

### `choice`

Selects one option from a required map of option name to description. Returns the chosen option, a probability distribution over all options, and a confidence value.

A description can be `null` when the option name is self-explanatory and needs no further explanation, for example `{ "shipping": null, "technical": null, "other": null }`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.concentrate.ai/v1/decisions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -d '{
      "model": "jev-1.13.0",
      "state": {
        "subject": "Re: order #4471",
        "body": "My package was marked as delivered but it never arrived."
      },
      "questions": {
        "category": {
          "type": "choice",
          "instructions": "Categorize this support ticket.",
          "criteria": {
            "shipping": "Questions about deliveries, tracking, or returns",
            "technical": "Questions about product bugs or errors",
            "account": "Questions about login, access, or account settings"
          }
        }
      }
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.concentrate.ai/v1/decisions",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Content-Type": "application/json"
      },
      json={
          "model": "jev-1.13.0",
          "state": {
              "subject": "Re: order #4471",
              "body": "My package was marked as delivered but it never arrived."
          },
          "questions": {
              "category": {
                  "type": "choice",
                  "instructions": "Categorize this support ticket.",
                  "criteria": {
                      "shipping": "Questions about deliveries, tracking, or returns",
                      "technical": "Questions about product bugs or errors",
                      "account": "Questions about login, access, or account settings"
                  }
              }
          }
      }
  )

  print(response.json()["answers"]["category"])
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.concentrate.ai/v1/decisions", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "jev-1.13.0",
      state: {
        subject: "Re: order #4471",
        body: "My package was marked as delivered but it never arrived."
      },
      questions: {
        category: {
          type: "choice",
          instructions: "Categorize this support ticket.",
          criteria: {
            shipping: "Questions about deliveries, tracking, or returns",
            technical: "Questions about product bugs or errors",
            account: "Questions about login, access, or account settings"
          }
        }
      }
    })
  });

  const data = await response.json();
  console.log(data.answers.category);
  ```
</CodeGroup>

### `score`

A position on ordered levels, described by a required array of one to ten level descriptions, ordered from lowest to highest. Two or more is what makes the position meaningful. Returns a probability-weighted value, the levels as a legend, a distribution, and a confidence value.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.concentrate.ai/v1/decisions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -d '{
      "model": "jev-1.13.0",
      "state": "I tried to cancel three times and support never replied. Extremely frustrating.",
      "questions": {
        "urgency": {
          "type": "score",
          "instructions": "How urgent is this message?",
          "criteria": [
            "Low: no action needed soon",
            "Medium: should be addressed within a few days",
            "High: needs a response today"
          ]
        }
      }
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.concentrate.ai/v1/decisions",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Content-Type": "application/json"
      },
      json={
          "model": "jev-1.13.0",
          "state": "I tried to cancel three times and support never replied. Extremely frustrating.",
          "questions": {
              "urgency": {
                  "type": "score",
                  "instructions": "How urgent is this message?",
                  "criteria": [
                      "Low: no action needed soon",
                      "Medium: should be addressed within a few days",
                      "High: needs a response today"
                  ]
              }
          }
      }
  )

  print(response.json()["answers"]["urgency"])
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.concentrate.ai/v1/decisions", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY",
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "jev-1.13.0",
      state: "I tried to cancel three times and support never replied. Extremely frustrating.",
      questions: {
        urgency: {
          type: "score",
          instructions: "How urgent is this message?",
          criteria: [
            "Low: no action needed soon",
            "Medium: should be addressed within a few days",
            "High: needs a response today"
          ]
        }
      }
    })
  });

  const data = await response.json();
  console.log(data.answers.urgency);
  ```
</CodeGroup>

## Response

```json theme={null}
{
  "id": "req_01a0cb13360d7a7d8af625155408796f",
  "model": "typesafe/jev-1.13.0",
  "answers": {
    "is_bug_report": {
      "type": "noul",
      "noul": 0.94
    }
  },
  "usage": {
    "input_tokens": 283,
    "output_tokens": 23
  }
}
```

`id` identifies the request; it's the Response ID the logs page shows, so quote it when you contact support. `answers` is keyed by the same question ids you sent in the request. `model` is returned in canonical `provider/model` form, for example `typesafe/jev-1.13.0`. `usage.output_tokens` is the count TypeSafe reports; output tokens aren't charged.

Each answer's shape depends on its `type`, which matches the question that produced it:

* **`noul`** — `{ "type": "noul", "noul": 0.87 }`. The single `noul` field is the probability of yes. There's no `confidence` field, because the probability is the answer.
* **`choice`** — `{ "type": "choice", "choice": "shipping", "confidence": 0.91, "probabilities": { "shipping": 0.91, "technical": 0.06, "account": 0.03 } }`. `confidence` and `probabilities` are both optional.
* **`score`** — `{ "type": "score", "score": 2.4, "confidence": 0.78, "probabilities": { "Low": 0.05, "Medium": 0.2, "High": 0.75 }, "legend": { "Low": "Low: no action needed soon", "Medium": "Medium: should be addressed within a few days", "High": "High: needs a response today" } }`. `confidence`, `probabilities` and `legend` are all optional. `legend` maps each level back to the description you supplied, so you can render the answer without holding onto the original request.

On `choice` and `score` answers, `confidence` summarizes how concentrated the probability distribution is. It isn't a statement that the chosen answer is correct: several genuinely acceptable options will spread probability across them and lower `confidence` even when the model is judging soundly.

<Note>
  Fields that TypeSafe returns beyond what's documented here are forwarded to you rather than stripped. If you're writing a strict client-side schema, allow for unknown fields on the response and on individual answers.
</Note>

## Redaction, ZDR and HIPAA

Keys with redaction enabled can't use this endpoint: the request is refused with a `400` before anything is sent upstream. Redaction runs on chat bodies, and a Decisions request has no chat body, so accepting it would send your `state` to TypeSafe unredacted. Use a key without redaction enabled for Decisions traffic.

TypeSafe offers neither zero data retention nor a Business Associate Agreement, so keys with ZDR or HIPAA mode enabled are refused with a `422`, exactly as the chat endpoints refuse a provider that can't meet the policy.

## Model and aliases

The current model is `jev-1.13.0`. The following aliases resolve to it:

* `jev-latest`
* `jev-preview`
* `jev`
* `jev-1.13` (the slug OpenRouter publishes as `typesafe/jev-1.13`)

A `provider/` prefix is accepted: `typesafe/jev-1.13.0` and `typesafe/jev-1.13` both work. A prefix that isn't a provider of the model, such as `openai/jev-1.13.0`, is a `400`.

<Note>
  Aliases move when TypeSafe ships a new release, so the answers behind `jev-latest` can change without a change on your side. The response's `model` field reports the versioned ID that answered. If you've tuned confidence thresholds against a version, pin its ID (`jev-1.13.0`) and move on your own schedule.
</Note>

## Limits

| Limit | Value |
| - | - |
| Tokens per request | 64k |
| `state` plus the longest question | 32k |
| Choice options | up to 255 |
| Score levels | 1 to 10 |
| Input token price | \$0.042 per million |
| Output tokens | not charged |

Decisions requests share your key's token rate-limit windows with every other endpoint. During the beta there's no per-request limit beyond that, but all Concentrate traffic to TypeSafe shares one account limit of 1,200 requests per minute; if you plan a sustained high-volume workload, contact us first.

## Errors

| Status | Meaning |
| - | - |
| `400` | The request is malformed, the model is unknown or isn't a Decisions model, the `provider/` prefix doesn't match, the key has redaction enabled, or the request exceeds a token budget (`max_tokens_exceeded`) |
| `401` | Missing or invalid API key |
| `402` | Insufficient credit, or the key's spend cap is reached |
| `403` | The key's settings don't allow this model or provider |
| `404` | The path doesn't exist |
| `422` | The key has ZDR or HIPAA mode enabled, or TypeSafe rejected the request's shape (the message is theirs) |
| `424` | The upstream TypeSafe request failed |
| `429` | Your token rate limit, or TypeSafe's, was hit; a `retry-after` header is forwarded when TypeSafe sends one |
| `499` | You closed the connection before we answered; recorded for your usage, never delivered |
| `504` | TypeSafe didn't respond in time |

See [Error Handling](/docs/api-reference/endpoint/errors) for the general error response format.

## Related Documentation

<CardGroup cols={2}>
  <Card title="Writing questions" icon="pen" href="https://docs.typesafe.ai/concepts/how-to-build-with-system-one">
    TypeSafe's guide to state, instructions and criteria
  </Card>

  <Card title="Jev 1.13 jaggedness" icon="triangle-exclamation" href="https://docs.typesafe.ai/model-jaggedness/jev-1.13">
    Known failure modes and what to do instead
  </Card>

  <Card title="Create Response" icon="message" href="/docs/api-reference/endpoint/create-response">
    Generate text with the Responses API
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/docs/api-reference/endpoint/errors">
    Understand error responses across the API
  </Card>
</CardGroup>
