divergent/docs

Rubric is a small model that writes two things: a title for a chat, in 2 to 7 words, and a preview of an item in a list, in at most 15 words, telling the reader what they would learn by opening it.

You send the chat or the item as data, not a prompt. The control plane renders it exactly as the model was trained on, asks the model, checks the line that comes back, and returns it.

curl https://api.divergentlabs.xyz/api/inference/rubric/v1/titles \
  -H "Authorization: Bearer $DIVERGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "why does my docker container exit immediately?"}]}'
{
  "object": "title",
  "model": "rubric",
  "text": "Docker container exiting immediately",
  "fallback": false,
  "usage": { "input_tokens": 45, "output_tokens": 6 },
  "latency_ms": 363
}

The key needs the inference:invoke scope. Keys made by div login don’t have it; make one under Settings in the dashboard. To try a request before writing code, use Playground in the dashboard. Its curl changes as you edit the form.

Titles

POST /api/inference/rubric/v1/titles

Field
messages the chat, oldest first. Each message has content and a role (user, assistant) or a name, or both.

A long chat is cut down before the model reads it: each message to its first 4,200 and last 1,800 characters, and the chat to about 12,000 characters. Messages past that are counted instead of included.

Previews

POST /api/inference/rubric/v1/previews

curl https://api.divergentlabs.xyz/api/inference/rubric/v1/previews \
  -H "Authorization: Bearer $DIVERGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "email",
    "reader": "Sam Haddad",
    "item": {
      "from": "Ines Okafor",
      "to": "Sam Haddad",
      "subject": "Re: coffee",
      "body": "Hey Sam! Want to come to the Harbour Street café instead? We could work from there afterwards."
    }
  }'
{
  "object": "preview",
  "model": "rubric",
  "text": "Ines suggests meeting at the Harbour Street café to work afterwards.",
  "fallback": false,
  "usage": { "input_tokens": 93, "output_tokens": 14 },
  "latency_ms": 518
}
Field
kind email, message, ticket, issue, event, doc or agent_action
reader who is looking at the list. Their name is marked “(you)” wherever it appears, so a message they sent comes back as “You offered to…”. Optional; without it, nobody in the item is “you”.
item the item, below

Every item field is a string, and all of them are optional:

Field
subject, title, channel, status, when, where headers, shown in this order
from, to, cc names, comma-separated. The reader’s name is marked in each.
date as you would show it
body the newest message. Its first 8,400 and last 3,600 characters are kept.
thread the messages before the newest, in the shape messages takes for a title. Only the newest is summarized; the thread is context.

An agent_action is a tool call, and takes different fields:

Field
tool the tool’s name. Required.
arguments an object, or a string
result what the tool returned, as a string. Leave it out or send null while the tool is still running, and the preview reads “Searching the web for…”.
context what the agent was doing, if you have it

Previews are in English, whatever language the item is in. language is accepted only as "English".

The answer

text is one line, with no quotes and no label. Before it is returned it is checked: a title must be 2 to 8 words with no quotes, no emoji and no full stop at the end; a preview must be 1 to 20 words and at most 160 characters; both must be English and on one line.

A line that fails a check is not returned. text is a fallback built from the input instead, and fallback is true:

Request Fallback
a title New conversation
an agent_action Running <tool>…, or Ran <tool> once there is a result
any other preview the subject, else the title, else the body’s first line, cut at 15 words

The request is billed either way, since the model read the input either way.

Price

$0.10 per million input tokens. Output is free.

usage is the tokens the model reports for the request. Nothing is charged per request. The meter adds up each org’s tokens for each UTC day, prices the day, and posts what has not been charged yet, so the day is rounded up to a cent once rather than on every request. The charges show on the Billing page by model. Each request shows under Endpoints → Requests, with the key that made it. latency_ms is the model’s time for the request, the same figure that list shows, so it leaves out the network between us and you.

A request needs credit. An org at zero gets a 402 before the model is asked.

Errors

Errors have the same shape as a chat completion’s, with code to branch on and message for a person to read:

{ "error": { "code": "invalid_request", "type": "invalid_request_error", "message": "item.tool: required, as a string", "param": "item.tool" } }
Status code When
401 unauthorized no key, or a key that has been revoked
402 insufficient_credit the org’s credit is used up
403 insufficient_scope the key lacks inference:invoke
404 model_not_found the path names a model that does not write titles and previews
422 invalid_request a field is missing or the wrong type. param is its path, such as messages[0].content.
502 upstream_unavailable the model did not answer. Nothing was charged.
503 not_configured the model is not deployed where this control plane can reach it
esc