# Decisions API

> Call a saved Jev decision with POST /v1/decisions/{slug}: outcome, confidence band, pins, deadline and fallback, single or batches of up to 500 items.

Source: https://infro.io/docs/api/decisions

> **Note:** INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See [Early access](https://infro.io/early-access).

A decision policy is a saved question for Jev with INFRO's thresholds around it. Your application sends only the facts (`state`); the question, what each outcome means, the confidence bands, pinned outcomes, the deadline and the fallback live in the policy, edited in the console under Decisions and versioned.

> **Note:** Every Jev call runs on **your own TypeSafe connection** and your TypeSafe account bills it; every other [decision model](https://infro.io/docs/features/decision-models) runs on your own account with its provider. INFRO charges nothing per decision; the estimated provider cost of each call is on its row in the request log, separate from your INFRO subscription.

## Decide

```bash
curl https://api.infro.io/v1/decisions/tool-gate \
  -H "Authorization: Bearer $INFRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "state": {
      "request": "Tidy up my inbox",
      "tool": { "name": "delete_emails", "arguments": { "older_than_days": 30 } }
    }
  }'
```

- `state` (string | object | array, required) — The facts the decision is about. At most 128 KB as JSON. Never stored.
- `deadline_ms` (integer) — Overrides the policy's deadline for this call, 50–10000 ms.
- `metadata` (object) — Attribution and tags, as on every endpoint. Recorded on the row.

```json
{
  "id": "req_…",
  "object": "decision",
  "policy": "tool-gate",
  "version": 3,
  "outcome": "ask",
  "label": "Should run only after the user confirms: it changes or deletes data, spends money, or contacts other people.",
  "band": "confirm",
  "confidence": 0.71,
  "source": "model",
  "probabilities": { "allow": 0.12, "ask": 0.81, "deny": 0.07 },
  "score": null,
  "latency_ms": 150,
  "model": "typesafe/jev-1.13",
  "usage": { "input_tokens": 412, "output_tokens": 0, "cost": 0.0000173, "price_source": "list" },
  "route": "primary",
  "route_name": null,
  "provider": "typesafe",
  "connection": "conn_…"
}
```

| Field | Meaning |
| --- | --- |
| `outcome` | `yes`/`no` for a yes-or-no question, an option key for a choice, a level index for a score. |
| `label` | What the outcome means, in the policy's own words. |
| `band` | `act`, `confirm` or `human`, from the confidence and the policy's thresholds. |
| `confidence` | 0 to 1. A pin answers 1; a fallback answers 0. |
| `source` | `model`, `pin` (decided by rule before any model call) or `fallback`. |
| `probabilities` | Per outcome. Null for a pin or a fallback. |
| `score` | Score questions: the expected level, which can sit between two levels. |
| `fallback_reason` | Present when the fallback answered: the deadline passed or the provider failed. |

```python
import os
import requests

answer = requests.post(
    "https://api.infro.io/v1/decisions/inbox-triage",
    headers={"Authorization": f"Bearer {os.environ['INFRO_API_KEY']}"},
    json={"state": {"from": "dana@customer.example", "subject": "Invoice before Friday", "body": "…"}},
    timeout=10,
).json()

if answer["band"] == "act":
    route_email(answer["outcome"])
elif answer["band"] == "confirm":
    ask_user_to_confirm(answer["outcome"])
else:
    send_to_a_person()
```

## Pins

A pin decides by rule before any model call: when a dot path into the state (`tool.name`) is, is one of, starts with, ends with or contains a value, the outcome is fixed. The first matching pin answers with `source: "pin"`, confidence 1 and band `act`, costs nothing and needs no connection. Use pins for the cases that must never depend on a classifier — a tool that may never run.

## Deadline and fallback

A policy can set a deadline. When Jev has not answered by then — or when every connection fails — the policy's fallback answers with `source: "fallback"`, the band the policy chose for it and a `fallback_reason`. A missed deadline is never counted against the health of your connection. With no fallback, a missed deadline is `408 decision_deadline` and a provider failure is the usual chain-exhausted error.

## Batches

```bash
curl https://api.infro.io/v1/decisions/ticket-labels/batch \
  -H "Authorization: Bearer $INFRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "concurrency": 8,
    "items": [
      { "id": "t-1001", "state": { "title": "Charged twice", "body": "…" } },
      { "id": "t-1002", "state": { "title": "Export hangs", "body": "…" } }
    ]
  }'
```

Up to 500 items per request, decided on one of your connections at the chosen concurrency (default 8). Each result carries its `index` and your `id`. An item your provider rejects is that item's `error`; the others are still decided. `summary` counts outcomes, bands and sources (and the mean score for a score question), so a large table is labelled as a series of batches whose summaries add up. A batch is one row in the request log.

## List policies

`GET /v1/decisions` lists the enabled policies the key may call, with each one's question type, outcomes and what they mean, bands, pin count, fallback and deadline. A policy limited to a project is listed only for that project's keys.

## Eval monitors

A policy with an eval monitor scores a share of your served chat completions in the background, after the answer has been returned: the policy sees `{prompt, response}` (the latest user message and the model's answer). Each score is its own call to the policy's decision model, on your own account with its provider, recorded under the request it judged; the policy's console page shows the mean score per served model. Answers served from INFRO's response cache are not scored again.

## Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `decision_policy_not_found` | No enabled policy with that slug that this key may call. |
| 400 | `state_too_large` | The state is larger than 128 KB. |
| 400 | `batch_too_large` | More than 500 items in a batch. |
| 408 | `decision_deadline` | The deadline passed and the policy has no fallback. |
| 502 | `unreadable_decision` | The provider answered without a readable decision and the policy has no fallback. |
