Documentation · Quickstart

Quickstart

Beta. Field names may still change before general availability.

1. Get an API key

Sign up at spinf.com, 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:

export SPINF_API_KEY="ssk-..."

New accounts start with $4.50 of free credit.

Working with a coding agent?

Give it the spinf skill: the request rules, how to read the scores, how to design questions, cost and errors, in one file. In Claude Code:

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. 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.

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.

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):

{
  "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 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.
  • 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.

Raw Markdown for agents: /docs/quickstart.md · /llms.txt