Rubric
Writes a chat's title and a one-line preview of an email, message, ticket or agent action. Send the item as JSON and get one line back, at $0.10 per million input tokens with output free.
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 |