curl -X POST https://api.60db.ai/judge/rubrics \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
}'
const response = await fetch('https://api.60db.ai/judge/rubrics', {
method: 'POST',
headers: {
'Authorization': 'Bearer your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
}),
});
const data = await response.json();
import requests
response = requests.post(
"https://api.60db.ai/judge/rubrics",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
},
)
data = response.json()
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
"name": "Docs sample 22592",
"description": "Used to capture a real response for the docs",
"model": "jev-latest",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"unknown": "Unclear",
"dismissive": "Brushes off",
"professional": "Calm"
}
}
},
"question_count": 1,
"shared": false,
"is_mine": true,
"created_at": "2026-09-28T13:19:50.210Z",
"updated_at": null
}
}
Judge
Create rubric
Save a set of questions so they can be re-run by id
POST
/
judge
/
rubrics
curl -X POST https://api.60db.ai/judge/rubrics \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
}'
const response = await fetch('https://api.60db.ai/judge/rubrics', {
method: 'POST',
headers: {
'Authorization': 'Bearer your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
}),
});
const data = await response.json();
import requests
response = requests.post(
"https://api.60db.ai/judge/rubrics",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
},
)
data = response.json()
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
"name": "Docs sample 22592",
"description": "Used to capture a real response for the docs",
"model": "jev-latest",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"unknown": "Unclear",
"dismissive": "Brushes off",
"professional": "Calm"
}
}
},
"question_count": 1,
"shared": false,
"is_mine": true,
"created_at": "2026-09-28T13:19:50.210Z",
"updated_at": null
}
}
Save a rubric once and run it by
rubric_id from then on.
Questions are validated with exactly the same rules as a live evaluation, so a rubric that saves is a rubric that runs. They are re-validated on every run too — a rubric is not trusted just because it was valid when stored.
Request
Headers
string
required
Bearer token — your standard 60db API key (
sk_…), or a user JWT. There is no separate Judge credential.string
required
application/json
Body
string
required
Unique per workspace, max 120 characters.
string
What the rubric is for. Max 2,000 characters.
string
default:"jev-latest"
Default model for runs of this rubric. A request may still override it.
boolean
default:"false"
Publish to the whole workspace. Owner or admin only — a member gets
403.Response
object
The saved rubric, including its new
id. Returns 201.Errors
| Status | Meaning |
|---|---|
400 | Missing name, or an invalid question — the message names it. |
403 | shared: true requires owner or admin. |
409 | A rubric with that name already exists in this workspace. |
Example
curl -X POST https://api.60db.ai/judge/rubrics \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
}'
const response = await fetch('https://api.60db.ai/judge/rubrics', {
method: 'POST',
headers: {
'Authorization': 'Bearer your-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
}),
});
const data = await response.json();
import requests
response = requests.post(
"https://api.60db.ai/judge/rubrics",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"name": "Call QA",
"description": "Tone, satisfaction and outcome on inbound calls",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"professional": "Calm",
"dismissive": "Brushes off",
"unknown": "Unclear"
}
}
},
"shared": false
},
)
data = response.json()
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
"name": "Docs sample 22592",
"description": "Used to capture a real response for the docs",
"model": "jev-latest",
"questions": {
"tone": {
"type": "choice",
"criteria": {
"unknown": "Unclear",
"dismissive": "Brushes off",
"professional": "Calm"
}
}
},
"question_count": 1,
"shared": false,
"is_mine": true,
"created_at": "2026-09-28T13:19:50.210Z",
"updated_at": null
}
}