# Decisions

Answer typed questions about an input with any model of a project, through an endpoint compatible with OpenAI's Decisions API.

`POST /v1/decisions` answers typed questions about an input, with probabilities. It is compatible with [OpenAI's Decisions API](https://developers.openai.com/api/reference/resources/decisions/methods/create).

> **Warning**
>
> Decisions needs Spice runtime **v2.4.0-rc.2 or later**. Projects pinned to an earlier version return `404`. See [Spice Runtime Versions](https://spice.ai/docs/cloud/portal/app-spicepod/spice-runtime-versions).

Any model in the Spicepod of the project can answer:

| Model | Example `from` | Probabilities |
| - | - | - |
| TypeSafe decision model | `typesafe:jev` | Calibrated by the model. |
| OpenAI decision model | `openai:gpt-6-luna` | The model's estimates. |
| Chat model | `openai:gpt-4o-mini`, `anthropic:claude-haiku-4-5` | The model's own estimates, which are not calibrated. |

## Create a decision

`POST` `https://data.spiceai.io/v1/decisions`

### Headers

| Header | Type | Description |
| - | - | - |
| `X-API-Key` | string | Your project API key |
| `Content-Type` | string | `application/json` |

### Body

| Field | Type | Description |
| - | - | - |
| `model` | string | The name of a model under `models` in the Spicepod. Required. |
| `input` | string or array | The text that every question is about, or user messages with text parts. Image parts return `400`. Required. |
| `questions` | array | 1 to 200 questions, answered in order. Required. |
| `reasoning_effort` | string | `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`. Spice passes it to a chat model. A decision model returns `400` for it. A Spice extension. |
| `safety_identifier` | string | An opaque ID for the end user, at most 128 characters. Spice forwards it to OpenAI decision models. |

A field that this table does not list returns `400`.

`input` can also be a list of user messages. Each message has the `user` role, and its `content` is a string or a list of `input_text` parts. An `input_image` part returns `400`.

```json
"input": [
  {"role": "user", "content": [{"type": "input_text", "text": "Help! My payouts have been failing for 3 days."}]}
]
```

Each question has a `type`, its `instructions`, and an optional `name` that its answer repeats:

| `type` | Asks | Adds |
| - | - | - |
| `predicate` | How likely a statement about the input is true. | Nothing. |
| `choice` | Which option fits the input. | `choices`: 2 to 255 distinct options, each a `value` with an optional `description`. |
| `score` | Where the input sits on ordered levels. | `levels`: 2 to 10 levels, lowest first, each a `label` with an optional `description`. |

```bash
curl -X POST "https://data.spiceai.io/v1/decisions" \
  -H "X-API-Key: <project-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev",
    "input": "Help! My payouts have been failing for 3 days.",
    "questions": [
      {"type": "predicate", "name": "urgent", "instructions": "Does this convey urgency?"},
      {"type": "choice", "name": "team", "instructions": "Which team should handle this?",
       "choices": [{"value": "billing", "description": "Payments and payouts"}, {"value": "technical", "description": "Bugs and outages"}]},
      {"type": "score", "name": "tone", "instructions": "How upset is the customer?",
       "levels": [{"label": "calm"}, {"label": "annoyed"}, {"label": "furious"}]}
    ]
  }'
```

### Response

`answers` holds one answer per question, in question order. Each answer has the question's `type` and `name`, or `null` when the question has no name.

| `type` | Fields |
| - | - |
| `predicate` | `probability`: the probability that the statement is true. |
| `choice` | `choice`: the most probable option. `probabilities`: each option's probability. `confidence`: 0 for a uniform distribution, 1 for a certain one. |
| `score` | `score`: the probability-weighted, 0-based index of the levels. `probabilities` and `confidence`, as for `choice`. |
| `refusal` | None. The model declined this question, and the other answers stand. |

```json
{
  "model": "jev",
  "answers": [
    {"type": "predicate", "name": "urgent", "probability": 0.9},
    {"type": "choice", "name": "team", "choice": "billing",
     "probabilities": [{"value": "billing", "probability": 0.8}, {"value": "technical", "probability": 0.2}], "confidence": 0.6},
    {"type": "score", "name": "tone", "score": 1.3,
     "probabilities": [{"value": 0, "label": "calm", "probability": 0.1}, {"value": 1, "label": "annoyed", "probability": 0.5}, {"value": 2, "label": "furious", "probability": 0.4}], "confidence": 0.3}
  ],
  "usage": {"input_tokens": 612, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0},
            "output_tokens": 0, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 612}
}
```

`model` is the name that the request used. `usage` is absent when the model reports no token counts.

### Errors

Errors use OpenAI's envelope: `{"error": {"message", "type", "param", "code"}}`.

| Status | Cause |
| - | - |
| `400` | The request is not valid. Unknown fields, image inputs, and `reasoning_effort` for a decision model return it. |
| `401` | The model provider rejected the credentials. |
| `403` | The model provider denied access. |
| `404` | No model has this name. |
| `429` | The request was rate limited. |
| `500` | The model could not answer. |
| `503` | The model provider is unavailable. |

## In the Portal

The Playground has a **Decisions** page that sends these requests to the project. See [Playground](https://spice.ai/docs/cloud/portal/playground).
