---
name: decide
description: "Use when the user wants typed judgments over their own data from Claude Code — yes/no probabilities, a labelled choice, or a rubric score over text or images, many items at a time. BlockRun's Decisions endpoint (OpenAI Decisions-compatible, gpt-6-luna): served only at POST api.blockrun.ai/v1/decisions, free with a BlockRun key (no x402 rail). Not an MCP tool: call it with curl. Replaces the OpenJev-backed /v1/decide, which is retired."
triggers:
  - "decide"
  - "decisions"
  - "v1/decide"
  - "v1/decisions"
  - "decisions api"
  - "typed judgment"
  - "typed judgments"
  - "openjev"
  - "gpt-6-luna"
  - "classify with a small model"
  - "cheap classifier"
  - "judgment model"
  - "score these"
  - "label these"
  - "triage tickets"
---

# Decisions — typed judgments from Claude Code

`POST https://api.blockrun.ai/v1/decisions` takes an **input** (a message, a ticket,
a diff, a tool result, an image) and a list of **questions** with a fixed answer
space, and returns one typed answer per question — no prose to parse. It is
compatible with OpenAI's Decisions API and served by `gpt-6-luna`.

It is **not a BlockRun MCP tool**, on purpose: you are already a frontier model,
and for a one-off "is this urgent?" you are the better judge. The endpoint earns
its place when the user wants the *same fixed ruler over many items*, or is
prototyping a judgment they will later run from a pipeline **without** a model.
From Claude Code, call it with `curl` from the shell.

## Where to call it

| Endpoint | Auth | Price |
|---|---|---|
| `https://api.blockrun.ai/v1/decisions` | `Authorization: Bearer brk_live_…` | Free for any registered key, per-key hourly limit |

That is the only rail. Decisions is **not sold over x402**: the x402 per-call
floor is above what a Decisions call costs, so a paid route would bill nearly
every call at the floor. Do not look for it on `blockrun.ai` or
`sol.blockrun.ai`, and do not offer to pay for it from the wallet.
Batch the questions anyway: many questions in one call share one input and one
round trip.

**OpenJev is retired** (2026-10-07). The old `POST api.blockrun.ai/v1/decide`
shape (`state`, `noul`/`choice`/`score` with `criteria`) is still accepted and
answered by the same model, but write new calls against `/v1/decisions`.

## Getting a key without changing how the MCP pays

Keys are minted at <https://user.blockrun.ai/dashboard/keys> (`brk_live_…`,
shown once; registration, not a card).

For experiments, **export the key in the shell** and leave the MCP server alone:

```bash
export BLOCKRUN_API_KEY=brk_live_…
```

Do **not** write it to `~/.blockrun/.api-key` just to try this. The MCP server
reads that file at startup and a present key moves **every** paid tool from
wallet mode to account billing — the same switch `BLOCKRUN_API_KEY` in the MCP
server's own config makes. That is fine if the user wants account billing (see
the `blockrun-setup` skill); it is a surprise if they only wanted a free
judgment. If the MCP is already on account billing, the same key works for both.

## Request

```bash
curl -sS https://api.blockrun.ai/v1/decisions \
  -H "authorization: Bearer $BLOCKRUN_API_KEY" \
  -H "content-type: application/json" \
  -D /dev/stderr \
  -d '{
    "model": "gpt-6-luna",
    "input": "Help! My payouts have been failing for 3 days.",
    "questions": [
      { "type": "predicate", "name": "is_urgent",
        "instructions": "Does the message convey urgency?" },
      { "type": "choice", "name": "department",
        "instructions": "Which team should handle this?",
        "choices": [ { "value": "billing",   "description": "Payments, payouts, refunds" },
                     { "value": "technical", "description": "Bugs and outages" } ] },
      { "type": "score", "name": "frustration",
        "instructions": "How frustrated is the customer?",
        "levels": [ { "label": "calm",       "description": "Neutral tone" },
                    { "label": "frustrated", "description": "Clearly unhappy" },
                    { "label": "angry",      "description": "Hostile or threatening to leave" } ] }
    ]
  }'
```

`-D /dev/stderr` shows the response headers (rate limit) without mixing them
into the JSON on stdout.

| Field | Type | Required | Notes |
|---|---|---|---|
| `model` | string | no | `gpt-6-luna`, the only Decisions model. |
| `input` | string \| array | yes | A string, or messages whose `content` holds text and inline base64 images (no hosted URLs). If a fact matters, put it in — the model looks nothing up. |
| `questions` | array | yes | Each `{ type, name, instructions }`, plus `choices` or `levels`. |

| Type | You add | You get back |
|---|---|---|
| `predicate` | nothing | `probability` 0–1 |
| `choice` | `choices: [{ value, description }]` | `choice`, `probabilities` per value, `confidence` |
| `score` | `levels: [{ label, description }]`, lowest first | `score` (weighted level index from 0), `probabilities` per level, `confidence` |

A question the model declines comes back as `type: "refusal"`.

## Response (measured 2026-10-07 for the request above)

```json
{
  "model": "gpt-6-luna",
  "answers": [
    { "type": "predicate", "name": "is_urgent", "probability": 0.95 },
    { "type": "choice", "name": "department", "choice": "billing",
      "probabilities": [ { "value": "billing", "probability": 1.0 },
                         { "value": "technical", "probability": 0.0 } ],
      "confidence": 1.0 },
    { "type": "score", "name": "frustration", "score": 0.94,
      "probabilities": [ { "value": 0, "label": "calm", "probability": 0.06 },
                         { "value": 1, "label": "frustrated", "probability": 0.94 },
                         { "value": 2, "label": "angry", "probability": 0.0 } ],
      "confidence": 0.91 }
  ],
  "usage": { "input_tokens": 399, "output_tokens": 0, "total_tokens": 399 }
}
```

## Before you build a threshold

Set thresholds by running the user's **own labelled examples** through the
endpoint and looking at where the errors land — not by reading a number as an
accuracy. An agent that gates on `confidence > 0.8` without that check will be
wrong with no error to tell it so.

The model judges only the input. A question whose answer is not in it ("does
this need a reply today?" over a message that says nothing about timing) still
gets an answer that looks like a verdict. That one is policy — answer it in code.

## Trying it on the user's data — the shape that works

1. **Build the input** with everything the judgment needs. Fetch with
   `blockrun_exa` or `blockrun_search` first if a fact is missing.
2. **Ask the independent questions together** in one call.
3. **Combine with deterministic checks** the code already has.
4. **Route by margin.** Act automatically where the margin is wide *and* the
   checks agree; hand narrow cases to a reasoning model via `blockrun_chat`, or
   to a person.

A quick evaluation loop: put 20–50 labelled rows in a JSON file, loop `curl`
over them with `jq`, and tabulate agreement.

```bash
jq -c '.[]' examples.json | while read -r row; do
  input=$(jq -c '.input' <<<"$row"); want=$(jq -r '.label' <<<"$row")
  got=$(curl -sS https://api.blockrun.ai/v1/decisions \
    -H "authorization: Bearer $BLOCKRUN_API_KEY" -H "content-type: application/json" \
    -d "{\"model\": \"gpt-6-luna\", \"input\": $input, \"questions\": [{\"type\": \"choice\",
         \"name\": \"q\", \"instructions\": \"Which team should handle this?\",
         \"choices\": [{\"value\": \"billing\", \"description\": \"Payments, refunds\"},
                       {\"value\": \"technical\", \"description\": \"Bugs, outages\"}]}]}" \
    | jq -r '.answers[0].choice')
  echo "$want,$got"
done | sort | uniq -c
```

## Limits and errors

| Status | Meaning | Charged? |
|---|---|---|
| 400 | Body did not match the schema; the error names the field | No |
| 401 | Missing or invalid key | No |
| 429 | Per-key hourly limit; wait for `retry-after` | No |
| 502 / 504 | Model unreachable or timed out | No |

## Related

- API reference: <https://blockrun.ai/docs/api-reference/decisions>
- Account billing for the whole MCP: `blockrun-setup` skill, "API key" section.
