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

# Classification & scoring

> Get probabilities, categories, and scores from decision models through OpenAI Decisions or TypeSafe System One.

Decision models answer typed questions about an input with probabilities instead of writing text. Use them to detect intent, route a support request, or score urgency, and let the probability decide what your application does next.

Opper serves every decision model through two wire formats:

| Endpoint | Format | Answers natively | Input |
| - | - | - | - |
| `POST /v3/compat/decisions` | [OpenAI Decisions](https://developers.openai.com/api/docs/guides/decisions) | OpenAI's `gpt-6-luna` | Text and inline images for `gpt-6-luna`; text for other models |
| `POST /v3/compat/v1/systemone` | [TypeSafe System One](https://docs.typesafe.ai/api) | TypeSafe, Opper, Cloudflare, and Berget models | Text or structured context |

A model reached through the other format is translated both ways, so you can keep one client and switch models by name.

Both endpoints use your project-scoped Opper API key. Provider credentials are managed by Opper. Your organization needs a card on file, and your project's [model access rules](/control-plane/rules/model-access) and provider access requirements apply.

## Question types

| Decisions | System One | Use it for | Answer |
| - | - | - | - |
| `predicate` | `noul` | A yes/no question | The probability of yes, between 0 and 1. |
| `choice` | `choice` | Choosing a category | The chosen value, a `confidence`, and a probability for each option. |
| `score` | `score` | Scoring against ordered levels | A probability-weighted `score`, a `confidence`, and a probability for each level. Levels start at zero; the score can be fractional. |

You can include several questions in one request.

Set a project API key from [the platform](https://platform.opper.ai) for the examples below:

```bash theme={null}
export OPPER_API_KEY="op-your-project-api-key"
```

## OpenAI Decisions

Send OpenAI's Decisions format to `POST https://api.opper.ai/v3/compat/decisions`:

* `model`: `gpt-6-luna`, or the full catalog ID of another decision model, such as `typesafe/jev-1.13.0`.
* `input`: a string, or user messages whose `content` is a string or a list of `input_text` and `input_image` parts. Images must be inline base64 `data:` URLs. An image part takes an optional `detail` of `low`, `high`, `auto`, or `original`.
* `questions`: a list of `predicate`, `choice`, and `score` questions. Each requires `type` and `instructions` and takes an optional `name`, unique within the request. `choice` takes 2–255 `choices` whose `value` is a string or a boolean; `score` takes 2–10 ordered `levels`, each with a `label`.
* `safety_identifier` (optional): accepted for compatibility with OpenAI's SDK, then dropped. Opper neither forwards nor stores it; requests to OpenAI carry Opper's own identifier for your project.

Answers come back in question order, each with its question's `name`.

### Classify a support request

```bash theme={null}
curl --fail-with-body -sS \
  https://api.opper.ai/v3/compat/decisions \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "gpt-6-luna",
  "input": "I was charged twice for my October invoice. Please refund one of the two payments today.",
  "questions": [
    {
      "type": "predicate",
      "name": "refund",
      "instructions": "Does the customer ask for a refund?"
    },
    {
      "type": "choice",
      "name": "team",
      "instructions": "Which team should handle this?",
      "choices": [
        {"value": "billing", "description": "Payments and invoices"},
        {"value": "technical"},
        {"value": "sales"}
      ]
    },
    {
      "type": "score",
      "name": "urgency",
      "instructions": "How urgent is this?",
      "levels": [{"label": "Low"}, {"label": "Medium"}, {"label": "High"}]
    }
  ]
}'
```

Example response:

```json theme={null}
{
  "model": "gpt-6-luna",
  "answers": [
    {"type": "predicate", "name": "refund", "probability": 1.0},
    {
      "type": "choice",
      "name": "team",
      "choice": "billing",
      "probabilities": [
        {"value": "billing", "probability": 1.0},
        {"value": "technical", "probability": 0.0},
        {"value": "sales", "probability": 0.0}
      ],
      "confidence": 1.0
    },
    {
      "type": "score",
      "name": "urgency",
      "score": 1.48,
      "probabilities": [
        {"value": 0, "label": "Low", "probability": 0.02},
        {"value": 1, "label": "Medium", "probability": 0.48},
        {"value": 2, "label": "High", "probability": 0.5}
      ],
      "confidence": 0.22
    }
  ],
  "usage": {
    "input_tokens": 391,
    "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0},
    "output_tokens": 0,
    "output_tokens_details": {"reasoning_tokens": 0},
    "total_tokens": 391
  }
}
```

Choose a probability threshold in your application if you need a Boolean decision. Opper returns the probability without applying a threshold.

### Ask about an image

`gpt-6-luna` reads images. This request sends an 8×8 red PNG:

```bash theme={null}
curl --fail-with-body -sS \
  https://api.opper.ai/v3/compat/decisions \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "gpt-6-luna",
  "input": [
    {
      "role": "user",
      "content": [
        {"type": "input_text", "text": "Describe this image."},
        {"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEklEQVR4nGP4z8CAFWEXHbQSACj/P8Fu7N9hAAAAAElFTkSuQmCC"}
      ]
    }
  ],
  "questions": [
    {"type": "predicate", "name": "red", "instructions": "Is the image predominantly red?"}
  ]
}'
```

An example `answers`:

```json theme={null}
[{"type": "predicate", "name": "red", "probability": 0.98}]
```

Other decision models take text only and return `400` for image input.

### Use the OpenAI SDK

The OpenAI Python SDK calls this endpoint from version 3.26.0. Point it at Opper:

```python theme={null}
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://api.opper.ai/v3/compat",
    api_key=os.environ["OPPER_API_KEY"],
)

decision = client.decisions.create(
    model="gpt-6-luna",
    input="I was charged twice for my October invoice. Please refund one of the two payments today.",
    questions=[
        {"type": "predicate", "name": "refund", "instructions": "Does the customer ask for a refund?"},
    ],
)
print(decision.answers[0].probability)
```

### Declined questions

The model can decline a question. That answer is `{"type": "refusal", "name": "..."}` with no probability, and the other answers are unaffected. The request is billed as usual.

### Other decision models

Name any other decision model by its full catalog ID, such as `typesafe/jev-1.13.0` or `opper/kev-4b`. Opper translates the request to System One and the answer back, so the response has the same shape. For these models:

* Text parts join into one input, and image input returns `400`.
* Boolean choice values reach the model as the text `true` and `false`, so a question cannot hold both the string `"true"` and the boolean `true`.
* `usage` reports `input_tokens`, `output_tokens`, and `total_tokens`.

The support request above, sent with `"model": "typesafe/jev-1.13.0"`, answers:

```json theme={null}
{
  "model": "jev-1.13.0",
  "answers": [
    {"type": "predicate", "name": "refund", "probability": 0.99},
    {
      "type": "choice",
      "name": "team",
      "choice": "billing",
      "probabilities": [
        {"value": "billing", "probability": 1},
        {"value": "technical", "probability": 0},
        {"value": "sales", "probability": 0}
      ],
      "confidence": 1
    },
    {
      "type": "score",
      "name": "urgency",
      "score": 1.65,
      "probabilities": [
        {"value": 0, "label": "Low", "probability": 0},
        {"value": 1, "label": "Medium", "probability": 0.34},
        {"value": 2, "label": "High", "probability": 0.66}
      ],
      "confidence": 0.49
    }
  ],
  "usage": {"input_tokens": 377, "output_tokens": 69, "total_tokens": 446}
}
```

## TypeSafe System One

Send requests to `POST https://api.opper.ai/v3/compat/v1/systemone`.

Existing TypeSafe HTTP clients can change their base URL to `https://api.opper.ai/v3/compat` and authenticate with `Authorization: Bearer $OPPER_API_KEY`. Send the native `state` and `questions` format to `/v1/systemone`.

Every request contains:

* `model`: a decision model ID, such as `typesafe/jev-1.13.0`.
* `state`: the context to evaluate, as a string, object, or array.
* `questions`: a nonempty object of named questions. Each question requires `type` and `instructions`.

`choice` requires a `criteria` object with 1–255 categories. `score` requires an ordered `criteria` array with 2–10 levels. `noul` does not require criteria; you can optionally describe the `true` and `false` outcomes in a `criteria` object.

Answers use the same keys as your questions.

### Detect a refund request

```bash theme={null}
curl --fail-with-body -sS \
  https://api.opper.ai/v3/compat/v1/systemone \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "typesafe/jev-1.13.0",
  "state": "I was charged twice for my subscription. Please return the duplicate payment.",
  "questions": {
    "refund_requested": {
      "type": "noul",
      "instructions": "Is the customer requesting a refund?"
    }
  }
}'
```

Example response:

```json theme={null}
{
  "model": "jev-1.13.0",
  "answers": {
    "refund_requested": {
      "type": "noul",
      "noul": 0.98
    }
  },
  "usage": {
    "input_tokens": 287,
    "output_tokens": 21
  }
}
```

### Route a support request

```bash theme={null}
curl --fail-with-body -sS \
  https://api.opper.ai/v3/compat/v1/systemone \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "typesafe/jev-1.13.0",
  "state": {
    "subject": "Cannot authenticate",
    "message": "Our integration started returning 401 errors after rotating our API key."
  },
  "questions": {
    "team": {
      "type": "choice",
      "instructions": "Choose the team best suited to resolve this request.",
      "criteria": {
        "finance": "Charges, payments, and invoices",
        "engineering": "API and integration issues",
        "account_management": "Contracts and subscriptions"
      }
    }
  }
}'
```

An example `answers.team`:

```json theme={null}
{
  "type": "choice",
  "choice": "engineering",
  "confidence": 1.0,
  "probabilities": {
    "engineering": 1.0,
    "account_management": 0.0,
    "finance": 0.0
  }
}
```

### Score urgency

```bash theme={null}
curl --fail-with-body -sS \
  https://api.opper.ai/v3/compat/v1/systemone \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "typesafe/jev-1.13.0",
  "state": "Our production checkout is unavailable. Every customer payment has failed for the last 20 minutes.",
  "questions": {
    "urgency": {
      "type": "score",
      "instructions": "Rate the operational urgency.",
      "criteria": [
        "Low: informational request with no operational impact",
        "Medium: degraded functionality with a workaround",
        "High: production outage preventing customer payments"
      ]
    }
  }
}'
```

An example `answers.urgency`:

```json theme={null}
{
  "type": "score",
  "score": 2.0,
  "confidence": 1.0,
  "probabilities": {
    "0": 0.0,
    "1": 0.0,
    "2": 1.0
  },
  "legend": {
    "0": "Low: informational request with no operational impact",
    "1": "Medium: degraded functionality with a workaround",
    "2": "High: production outage preventing customer payments"
  }
}
```

With these three levels, scores range from 0 to 2.

### Use the TypeSafe SDK

The [TypeSafe SDK](https://docs.typesafe.ai/sdk) works with Opper unchanged: set its base URL to `https://api.opper.ai/v3/compat` and use your Opper API key. In Python (`typesafe-sdk`):

```python theme={null}
import os

from typesafe_sdk import Noul, TypeSafeClient

with TypeSafeClient(
    base_url="https://api.opper.ai/v3/compat",
    api_key=os.environ["OPPER_API_KEY"],
) as client:
    response = client.system_one(
        model="typesafe/jev-1.13.0",
        state="I was charged twice. Please refund the duplicate payment.",
        questions={
            "refund_requested": Noul(instructions="Is the customer requesting a refund?"),
        },
    )

print(response.nouls["refund_requested"].noul)
```

The TypeScript SDK (`@typesafe-ai/sdk`) works the same way; see [Drop-in SDKs](/build/gateway/drop-in-sdks#typesafe-sdks).

### OpenAI's model on System One

`gpt-6-luna` answers System One requests as `openai/gpt-6-luna-decisions`, text only. Opper sends structured `state` and `instructions` as compact JSON text and adds a `noul` question's `true` and `false` criteria to its instructions. A `choice` needs at least two categories. If the model declines a question, the request returns `422` naming it, and the call is billed because OpenAI bills it.

Answers and token counts can vary between calls.

## Choose a model

Discover decision models with:

```bash theme={null}
curl --fail-with-body -sS "https://api.opper.ai/v3/models?type=evaluation"
```

A bare model name means OpenAI's model on Decisions (`gpt-6-luna`) and TypeSafe's on System One (`jev-1.13.0`, `jev-latest`, and `jev-preview` resolve to TypeSafe catalog entries). Use the full catalog ID for every other model on either endpoint, such as `typesafe/jev-1.13.0`, `opper/kev-4b`, or `openai/gpt-6-luna-decisions`. The qualified aliases `typesafe/jev-latest` and `typesafe/jev-preview` are supported too.

Aliases follow Opper's approved catalog mapping. They do not automatically follow a new upstream TypeSafe release. The response's `model` identifies the upstream model that answered.

Jev 1.13 has a 64k total context limit, with a separate 32k limit for the state plus the longest question. TypeSafe enforces these limits using its own tokenizer.

<Note>
  Decision models, the catalog IDs `GET /v3/models?type=evaluation` lists (such as `typesafe/jev-1.13.0` and `openai/gpt-6-luna-decisions`), answer only on these two endpoints. Chat Completions and the other generation protocols do not accept them. A bare `gpt-6-luna` on those protocols is OpenAI's chat model, which works as usual. Both decision endpoints are synchronous; streaming, tools, and fields outside each format are unsupported.
</Note>

## Usage, cost, and traces

`gpt-6-luna` on Decisions returns OpenAI's response body unchanged, including its `usage` details. Translated Decisions responses carry `usage.input_tokens`, `usage.output_tokens`, and `usage.total_tokens`; System One responses carry `usage.input_tokens` and `usage.output_tokens`. Opper does not add a response envelope.

Billing uses the selected endpoint's catalog input and output token prices, including any long-context surcharge the catalog lists. See the [model catalog](https://opper.ai/models) for current pricing.

Add `-i` to a curl call to see these response headers:

| Header | Meaning |
| - | - |
| `X-Opper-Cost` / `X-Generation-Cost` | The call's cost in USD. |
| `X-Generation-Id` | Opper's generation ID. |
| `X-Opper-Trace-Id` | The trace UUID. |

Usage and billing are recorded for successful calls and for questions the model declined. Full input/output traces depend on your [Opper retention rule](/control-plane/rules/retention); in traces, each inline image appears as a placeholder with its size. Provider data policies are configured separately through [model access](/control-plane/rules/model-access); EU gateway hosting does not make every provider EU-hosted.

## Errors

| Status | Meaning |
| - | - |
| `400` | An invalid Decisions request, image input for a text-only model, an unknown, unavailable, or wrong-type model, or an upstream bad request. |
| `401` | Missing or invalid project API key. |
| `402` | Your organization has no card on file, or billing access is blocked. |
| `403` | Model access rules or required provider access prevent the request. |
| `413` | A Decisions request body over 50 MiB, or an oversized request the upstream provider rejected. |
| `422` | An invalid System One request, unsupported question types or fields, an upstream validation failure, or a question the model declined on System One. |
| `429` | Upstream rate limiting. For `gpt-6-luna`, Opper makes one attempt and passes OpenAI's `Retry-After` header through. |
| `502` | An upstream service or authentication failure prevented completion. |
| `503` | The provider or model configuration is unavailable. |
| `504` | The request timed out. |
| `529` | TypeSafe is temporarily overloaded. |

See the API reference for [Decisions](/v3-api-reference/compatibility/create-decision) and [System One](/v3-api-reference/compatibility/systemone), [OpenAI's Decisions guide](https://developers.openai.com/api/docs/guides/decisions), or [TypeSafe's API documentation](https://docs.typesafe.ai/api) for the native protocols.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.