# Quickstart

> **Beta.** Field names may still change before general availability.

## 1. Get an API key

Sign up at [spinf.com](/sign-up), open the console and create a key under **API keys**. Keys start with `ssk-` and are shown only once. Store the key in an environment variable:

```bash
export SPINF_API_KEY="ssk-..."
```

New accounts start with $4.50 of free credit.

### Working with a coding agent?

Give it the [spinf skill](https://spinf.com/skills/spinf/SKILL.md): the request rules, how to read the scores, how to
design questions, cost and errors, in one file. In Claude Code:

```bash
claude plugin marketplace add spinfinc/skills
claude plugin install spinf@spinfinc
```

In another agent, run `npx skills add spinfinc/skills --skill spinf` and select your agent. Or save the file in your
agent's skills folder, or point it at [llms.txt](https://spinf.com/llms.txt). The console's Quickstart has a ready-made prompt to paste into your agent.

## 2. Score some content

Your content goes in `messages`, as in a chat completion. A question is a **template** that is read after the
content and ends where the answer goes (`{?}`), with the **options** (the answers) to score there. A placeholder such
as `{commodity}` asks the same question for each value you list.

```bash
curl https://api.spinf.com/v1/score \
  -H "Authorization: Bearer $SPINF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "spinf-12b",
    "messages": [ { "role": "user", "content": "OPEC+ agreed on Sunday to extend its voluntary output cuts through the first quarter, while Saudi Arabia signaled it could deepen reductions if prices keep sliding. Analysts noted that US crude inventories fell for a third straight week as refinery runs reached a seasonal high." } ],
    "scoring": {
      "queries": [
        {
          "id": "direction",
          "template": "\n\nWill {commodity} prices rise or fall next month?\nAnswer:{?}",
          "options": [" rise", " fall"],
          "combinations": [ { "commodity": "crude oil" }, { "commodity": "natural gas" } ]
        },
        {
          "id": "supply_cut",
          "template": "\n\nDoes the article report a cut in oil supply?\nAnswer:{?}",
          "options": [" yes", " no"]
        },
        {
          "id": "topic",
          "template": "\n\nThe article is mainly about{?}",
          "options": [" oil", " natural gas", " coal"]
        }
      ]
    }
  }'
```

### With the OpenAI SDK

`/v1/chat/completions` takes the same body, so the OpenAI SDKs work as they are: put the `scoring` block in
`extra_body`.

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.spinf.com/v1", api_key=os.environ["SPINF_API_KEY"])
resp = client.chat.completions.create(
    model="spinf-12b",
    messages=[{"role": "user", "content": article_text}],
    extra_body={"scoring": {"queries": [{
        "id": "direction",
        "template": "\n\nWill {commodity} prices rise or fall next month?\nAnswer:{?}",
        "options": [" rise", " fall"],
        "combinations": [{"commodity": "crude oil"}, {"commodity": "natural gas"}],
    }]}},
)
for query in resp.model_extra["results"][0]["queries"]:
    for c in query["combinations"]:
        print(query["id"], c["values"], [(o["text"], o["p"], o["floor_p"]) for o in c["options"]])
```

The scores are in `resp.model_extra["results"]` (the response's `choices` is empty: nothing is generated).

## 3. Read the scores

The response of the call above (a live call to `spinf-12b`):

```json
{
  "id": "req_01M3B14ABQ0S81CBK2WQ5HPNX9",
  "object": "score",
  "created": 1790297909,
  "model": "spinf-12b",
  "system_fingerprint": "fp_87dd99dbf6",
  "results": [
    { "input_id": "0",
      "queries": [
        { "id": "direction",
          "combinations": [
            { "values": { "commodity": "crude oil" },
              "options": [
                { "text": " rise", "logprob": -2.813903, "p": 0.654929, "floor_p": 0.473377 },
                { "text": " fall", "logprob": -3.454679, "p": 0.345071, "floor_p": 0.526623 } ],
              "residual": 0.908432, "floor_residual": 0.936882 },
            { "values": { "commodity": "natural gas" },
              "options": [
                { "text": " rise", "logprob": -2.390459, "p": 0.444475, "floor_p": 0.605271 },
                { "text": " fall", "logprob": -2.167439, "p": 0.555525, "floor_p": 0.394729 } ],
              "residual": 0.793942, "floor_residual": 0.911571 } ] },
        { "id": "supply_cut",
          "combinations": [
            { "values": {},
              "options": [
                { "text": " yes", "logprob": -0.552571, "p": 0.834359, "floor_p": 0.435885 },
                { "text": " no", "logprob": -2.169414, "p": 0.165641, "floor_p": 0.564115 } ],
              "residual": 0.310287, "floor_residual": 0.323976 } ] },
        { "id": "topic",
          "combinations": [
            { "values": {},
              "options": [
                { "text": " oil", "logprob": -2.843978, "p": 0.990817, "floor_p": 0.815715 },
                { "text": " natural gas", "logprob": -7.552238, "p": 0.008938, "floor_p": 0.064336 },
                { "text": " coal", "logprob": -11.14758, "p": 0.000245, "floor_p": 0.119949 } ],
              "residual": 0.941267, "floor_residual": 0.999882 } ] } ] }
  ],
  "warnings": [],
  "usage": { "prompt_tokens": 148, "completion_tokens": 0, "total_tokens": 148,
             "prompt_tokens_details": { "content_tokens": 50, "query_tokens": 49, "floor_tokens": 49 },
             "billed_tokens": 1000 }
}
```

- `p`: the probability of each answer among the ones you listed (they sum to 1).
- `floor_p`: the same answer scored with empty content. The lift `p − floor_p` is what the content says: here the
  article makes " yes" to a supply cut go from 0.44 to 0.83, and " rise" for crude oil from 0.47 to 0.65. For natural
  gas it moves " rise" *below* its floor (0.44 against 0.61).
- `residual`: the probability that went to words other than your answers. It is mostly set by the template and the
  words, and it is naturally close to 1 for answers of several tokens (" natural gas"). Judge a question on `p` and
  the lift, and compare residuals across contents under the same question.
- `usage`: 148 tokens processed (the content once, 4 questions, their 4 empty floors), billed at the 1,000-token
  minimum per call.

See [Calibration](/docs/calibration) for how to use them.

## Tips

- **Content first, then the question.** The template is read after the content. Start it with a line break
  (`"\n\n…"`) and end it right where the answer goes.
- **Start each option with a space** (`" rise"`), and don't put a space before `{?}` as well: `"…about{?}"`, not
  `"…about {?}"` (that is refused with `option_boundary`).
- **Add a few labelled examples** to the content, before the item, when the zero-shot scores are not good enough:
  they are the biggest lever. See [Writing questions](/docs/writing-questions).
- **Use short, distinct answers.** One word each is best (`" rise"` / `" fall"`), and each answer should read
  naturally after the template.
- **Ask a grid in one template** with placeholders and `"combinations": "all"`.
- **Send many contents in one call** with `inputs`: the per-call minimum is shared, and so is the empty floor of every
  shared question.
- **Turn off the floor you already have.** The floor depends only on the question, so after a first call you can keep
  its `floor_p` and send `"empty_floor": false` on later calls with the same questions.
- The first request after an idle period may take a few seconds while capacity warms up.
