# Create decision

> **Preview.** Answers typed questions about an input with calibrated probabilities instead of generated text, in the format of OpenAI's Decisions API (`client.decisions.create` in OpenAI's Python SDK). Each question is a `predicate` (yes or no), a `choice` (one of several typed values) or a `score` (one of 2 to 10 ordered levels); the answers come back in question order.

Source: https://docs.serenityedge.ai/docs/api/edge/create-decision/

`POST https://api.serenityedge.ai/v1/decisions`

**Preview.** Answers typed questions about an input with calibrated probabilities instead of generated text, in the format of OpenAI's Decisions API (`client.decisions.create` in OpenAI's Python SDK). Each question is a `predicate` (yes or no), a `choice` (one of several typed values) or a `score` (one of 2 to 10 ordered levels); the answers come back in question order.

Only decision models can be used: `GET /v1/models?format=openai` lists them, each with `endpoints`. Only input tokens are billed. There is no streaming. Only Serenity Edge API keys can use this route; a key from a Serenity Star account gets 403 `permission_denied` for now. Until the service is enabled, the route answers 404 like an unknown path. See the [Decisions guide](https://docs.serenityedge.ai/docs/decisions).

## Request body (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | string | yes | Id of the decision model: an alias such as `orion-compass`, which follows the current version, or a versioned id such as `orion-compass-26.1`. `GET /v1/models?format=openai` lists the decision models, each with `endpoints`. |
| `input` | one of string, array of one of DecisionInputMessage (object), DecisionFunctionCall (object), DecisionFunctionCallOutput (object), DecisionCustomToolCall (object), DecisionCustomToolCallOutput (object) | yes | What the questions are about: a string, an array of user messages whose text parts are read in order, separated by a blank line, or a whole conversation (system, developer and assistant messages, tool calls and their outputs), read as a labelled transcript: see [Conversations](https://docs.serenityedge.ai/docs/decisions#conversations). `orion-compass-flash` and `d3` also read images in user messages: `input_image` parts with base64 data URLs, at most 4 images, 8 MiB in total once decoded and 16 megapixels each. `orion-compass` refuses them with HTTP 400. |
| `questions` | array of one of PredicateQuestion (object), ChoiceQuestion (object), ScoreQuestion (object) | yes | The questions to answer about the input, 1 to 256. The answers come back in the same order. At least 1 item. At most 256 items. |
| `questions[] (PredicateQuestion).type` | string | yes | Always `predicate`. |
| `questions[] (PredicateQuestion).name` | string | no | Optional name, unique in the request, repeated in the answer. |
| `questions[] (PredicateQuestion).instructions` | string | yes | The question, in natural language. |
| `questions[] (ChoiceQuestion).type` | string | yes | Always `choice`. |
| `questions[] (ChoiceQuestion).name` | string | no | Optional name, unique in the request, repeated in the answer. |
| `questions[] (ChoiceQuestion).instructions` | string | yes | The question, in natural language. |
| `questions[] (ChoiceQuestion).choices` | array of DecisionChoiceOption (object) | yes | The options, at most 255. Values must be distinct, also as text: a string and a boolean that read the same (`"true"` and `true`) cannot both be offered. At most 255 items. |
| `questions[] (ChoiceQuestion).choices[].value` | string or boolean | yes | The value returned in `choice` when this option is picked. |
| `questions[] (ChoiceQuestion).choices[].description` | string | no | What the option means. The model reads it. |
| `questions[] (ScoreQuestion).type` | string | yes | Always `score`. |
| `questions[] (ScoreQuestion).name` | string | no | Optional name, unique in the request, repeated in the answer. |
| `questions[] (ScoreQuestion).instructions` | string | yes | The question, in natural language. |
| `questions[] (ScoreQuestion).levels` | array of DecisionScoreLevel (object) | yes | The levels in order, from 2 to 10, with distinct labels. The first level is position 0. At least 2 items. At most 10 items. |
| `questions[] (ScoreQuestion).levels[].label` | string | yes | Name of the level. |
| `questions[] (ScoreQuestion).levels[].description` | string | no | What the level means. The model reads it. |
| `safety_identifier` | string | no | A stable identifier of your end user, as in OpenAI's API. Accepted for compatibility; like the rest of the body, it is never logged. |

Example:

```json
{
  "model": "orion-compass",
  "input": [
    {
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Ticket #4821. Customer: 'You charged my card twice for the same order last week. Refund the duplicate payment today or I will dispute it with my bank.'"
        }
      ]
    }
  ],
  "questions": [
    {
      "type": "predicate",
      "name": "refund",
      "instructions": "Is the customer asking for a refund?"
    },
    {
      "type": "choice",
      "name": "team",
      "instructions": "Which team should handle this ticket?",
      "choices": [
        {
          "value": "billing",
          "description": "Payments, charges and refunds"
        },
        {
          "value": "shipping",
          "description": "Delivery and tracking"
        },
        {
          "value": "technical",
          "description": "Bugs and sign-in problems"
        }
      ]
    },
    {
      "type": "score",
      "name": "urgency",
      "instructions": "How urgent is this ticket?",
      "levels": [
        {
          "label": "low",
          "description": "Can wait a week"
        },
        {
          "label": "medium",
          "description": "Within two days"
        },
        {
          "label": "high",
          "description": "Today, or the customer escalates"
        }
      ]
    }
  ]
}
```

## Responses

### 200: The answers, one per question in question order, with the versioned id of the model that answered and the token usage.

Body (`application/json`): `Decision`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `answers` | array of one of PredicateAnswer (object), ChoiceAnswer (object), ScoreAnswer (object) | yes | One answer per question, in question order. |
| `answers[] (PredicateAnswer).type` | string | yes | Always `predicate`. |
| `answers[] (PredicateAnswer).name` | string | no | The question's name, present when the question had one. |
| `answers[] (PredicateAnswer).probability` | number | yes | Probability that the answer is yes. At least 0. At most 1. |
| `answers[] (ChoiceAnswer).type` | string | yes | Always `choice`. |
| `answers[] (ChoiceAnswer).name` | string | no | The question's name, present when the question had one. |
| `answers[] (ChoiceAnswer).choice` | string or boolean | yes | The value of the most likely option, typed as in the request. On a tie, the option listed first. |
| `answers[] (ChoiceAnswer).confidence` | DecisionConfidence (number) | yes | How far the most likely option stands above chance: `(p_max - 1/n) / (1 - 1/n)` for `n` options or levels, 0 when they are all equally likely and 1 when one takes all the probability. At least 0. At most 1. |
| `answers[] (ChoiceAnswer).probabilities` | array of ChoiceProbability (object) | yes | The probability of every option, in the order of `choices`. They add up to 1. |
| `answers[] (ChoiceAnswer).probabilities[].value` | string or boolean | yes |  |
| `answers[] (ChoiceAnswer).probabilities[].probability` | number | yes | At least 0. At most 1. |
| `answers[] (ScoreAnswer).type` | string | yes | Always `score`. |
| `answers[] (ScoreAnswer).name` | string | no | The question's name, present when the question had one. |
| `answers[] (ScoreAnswer).score` | number | yes | The expected position on the scale: the probability-weighted mean of the level positions, from 0 (first level) to the number of levels minus one. At least 0. |
| `answers[] (ScoreAnswer).confidence` | DecisionConfidence (number) | yes | How far the most likely option stands above chance: `(p_max - 1/n) / (1 - 1/n)` for `n` options or levels, 0 when they are all equally likely and 1 when one takes all the probability. At least 0. At most 1. |
| `answers[] (ScoreAnswer).probabilities` | array of ScoreProbability (object) | yes | The probability of every level, in the order of `levels`. They add up to 1. |
| `answers[] (ScoreAnswer).probabilities[].value` | integer | yes | Position of the level, 0 for the first. At least 0. |
| `answers[] (ScoreAnswer).probabilities[].label` | string | yes |  |
| `answers[] (ScoreAnswer).probabilities[].probability` | number | yes | At least 0. At most 1. |
| `model` | string | yes | The versioned id of the model that answered (`orion-compass-26.1` when the request named `orion-compass`). |
| `usage` | DecisionUsage (object) | yes |  |
| `usage.input_tokens` | integer | yes | Tokens the model read to answer, images included, billed at the input price. The input is read with each question, so the count grows with the number of questions. |
| `usage.input_tokens_details` | object | no |  |
| `usage.input_tokens_details.cached_tokens` | integer | no | Always 0: cached input is not reported on decisions. |
| `usage.input_tokens_details.cache_write_tokens` | integer | no | Always 0. |
| `usage.input_tokens_details.text_tokens` | integer | no | Present only for a request with images: the text part of `input_tokens`, each image counted as its placeholder. |
| `usage.input_tokens_details.image_tokens` | integer | no | Present only for a request with images: the tokens the images expand to, over every question's read. Part of `input_tokens`: `text_tokens + image_tokens = input_tokens`. |
| `usage.output_tokens` | integer | yes | Always 0: the answer is read from the model's probabilities, no text is generated. |
| `usage.output_tokens_details` | object | no |  |
| `usage.output_tokens_details.reasoning_tokens` | integer | no | Always 0. |
| `usage.total_tokens` | integer | yes | Equal to `input_tokens`. |

### 400: The request was refused: malformed body, missing `model`, an unknown question type, an invalid option or level, an image the model cannot read, or an input longer than the model's context (`code: context_length_exceeded`). The message says what to fix and `param` points at the parameter when it is known.

Body (`application/json`): `ErrorResponse`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | Error (object) | yes | OpenAI-style error object. |
| `error.message` | string | yes | Human-readable description of the problem. |
| `error.type` | string | yes | Error category. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `rate_limit_error`, `server_error`. |
| `error.code` | string or null | no | Machine-readable error code, when available. |
| `error.param` | string or null | no | The request parameter the error refers to, when applicable. |

### 401: Missing or invalid API key.

Body (`application/json`): `ErrorResponse`, the same fields as under 400.

### 403: The key is valid but cannot use the decision API. For now only Serenity Edge API keys can; keys from a Serenity Star account cannot yet.

Body (`application/json`): `ErrorResponse`, the same fields as under 400.

### 404: The model is not a decision model (chat models cannot be used here), or the route is not enabled yet and answers like an unknown path.

Body (`application/json`): `ErrorResponse`, the same fields as under 400.

### 413: The request body is larger than the gateway accepts (4 MB, or 12 MiB on `orion-compass-flash` and `d3`).

Body (`application/json`): `ErrorResponse`, the same fields as under 400.

### 429: Rate limit exceeded, or the service is temporarily at capacity for this model. Wait for the number of seconds in the `Retry-After` header before retrying.

Header `Retry-After`: Seconds to wait before retrying.

Body (`application/json`): `ErrorResponse`, the same fields as under 400.

### 500: Unexpected server error: the model service returned an unexpected response (`upstream_error`), the request got no answer within 600 seconds (`timeout`), or an error in the gateway. Retry with backoff.

Body (`application/json`): `ErrorResponse`, the same fields as under 400.

### 503: The model service is temporarily unavailable (`upstream_unavailable`). Retry later and check the status page at https://status.serenityedge.ai.

Body (`application/json`): `ErrorResponse`, the same fields as under 400.
