Guides
Benchmark runs
Every first-party benchmark (counselbench-100, salesbench-100, devopsbench-100, ledgerbench-100) hosts an executable sandbox behind /api/v1/benchmarks/{slug}/sandbox. You can point any model at it — Claude, ChatGPT, Grok, DeepSeek, or your own OpenAI-compatible endpoint — and the benchmark’s own verifier grades what the model left behind. Nothing here is a leaderboard row: the leaderboard is the frozen 100-task release run on Harbor with traces. A hosted run is a self-run you can inspect, fetch, and publish as a community run below that leaderboard.
The single-task run
POST /api/v1/benchmarks/{slug}/runs with the model request shape (Models) and an optional max_steps(1–40, default 24) runs the benchmark’s public sandbox task inline, bounded at four minutes, and answers 201with the record: the grader’s score, strict_pass, passed_checks/total_checks, the recorded tool calls, the session evidence_url, and the exact full_suite_command to run the frozen release on Harbor with the same model. A bf_ key is required; a provider api_key on the request serves this run only and is never stored.
$ curl -sX POST https://blobfish.ai/api/v1/benchmarks/counselbench-100/runs \
-H 'X-API-Key: bf_YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"provider":"xai","model":"grok-4","api_key":"xai-…","max_steps":24}'
# → 201 { kind: "task", run_id: "bmr_…", score, strict_pass, steps[], evidence_url, full_suite_command }The same request is what the Run your model panel on each benchmark page sends when What to run is the public sandbox task.
The suite run
Add suite: "sample" (the first 10 hosted tasks, in bundle order) or suite: "full" (every hosted task) to the same POST and the server starts a background job instead: it answers 202 with the queued bmrs_ record, a Location to poll, and Retry-After: 5. max_tasks (1–100) bounds either suite. Each task runs through the same core as a single run — a fresh isolated session, the real MCP tools, the grader’s verdict — one at a time, and the record is updated after every task.
$ curl -si -X POST https://blobfish.ai/api/v1/benchmarks/counselbench-100/runs \
-H 'X-API-Key: bf_YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"provider":"xai","model":"grok-4","api_key":"xai-…","suite":"sample","max_steps":24}'
# HTTP/1.1 202 Accepted
# Location: https://blobfish.ai/api/v1/benchmarks/counselbench-100/runs/bmrs_…
# Retry-After: 5
$ RUN=bmrs_…
$ while :; do
curl -s https://blobfish.ai/api/v1/benchmarks/counselbench-100/runs/$RUN -H 'X-API-Key: bf_YOUR_KEY' > run.json
jq -r '"\(.status) \(.progress.done)/\(.progress.total)"' run.json
jq -e '.status == "queued" or .status == "running"' run.json > /dev/null || break
sleep "$(jq -r '.poll_after_seconds // 5' run.json)"
doneEvery poll answers the whole record:
| Field | Meaning |
|---|---|
kind | "suite" — distinct from a single-task "task" record. |
status | queued | running | completed | failed | interrupted. While in flight the response carries poll_after_seconds and Retry-After: 5. |
progress | { done, total } — tasks finished over tasks planned (task_ids lists the plan in run order). |
per_task[] | One row per finished task: task_id, status, the grader’s score / passed / passed_checks / total_checks, steps, finish, session_id, evidence_url, and error when it failed. |
aggregate | { mean_score, pass_rate, graded, passed } over graded tasks; null scores until one task is graded. |
coverage | { hosted_tasks, official_tasks, official_suite } — how many frozen tasks this host carries vs the release, and whether the run covered every official task. |
submission | null, or { community_run_id, submitted_at } once published. |
links | self (poll), submit, community, page (the benchmark page’s #community-runs). |
error | On failed / interrupted: { kind, message } — e.g. auth when the provider rejected the credential (the remaining tasks are not attempted), interrupted when the runner stopped. |
GET /api/v1/benchmarks/{slug}/runs lists your single runs (runs) and suite jobs (suite_runs) for that benchmark, newest first. One suite job runs per account at a time: a second POST answers 409 run_in_progress with the run_id to follow.
Publish it as a community run
A completedsuite run with at least one graded task can be published, once, onto the benchmark page’s community board — below the official leaderboard and never merged into it. The row carries your model, whose credential served it, the suite, tasks/graded/passed, mean score, pass rate, coverage, and the timestamps; every row is badged verbatim:
self-run · graded by the benchmark's own verifier · not the frozen official suite unless suite:full
$ curl -sX POST https://blobfish.ai/api/v1/benchmarks/counselbench-100/runs/$RUN/submit \
-H 'X-API-Key: bf_YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"display_name":"grok-4 · my scaffold","notes":"temperature 0, 24 turns"}' # ≤80 / ≤500 chars
# → 201 { community_run_id: "bcr_…", mine: true, badge, coverage, mean_score, pass_rate, links: { run, community, page, delete } }
$ curl -s https://blobfish.ai/api/v1/benchmarks/counselbench-100/community # public; best mean score first
$ curl -s https://blobfish.ai/api/v1/benchmarks/counselbench-100/community -H 'X-API-Key: bf_YOUR_KEY' # same board, your rows carry mine: true
$ curl -sX DELETE https://blobfish.ai/api/v1/benchmarks/counselbench-100/community/bcr_… -H 'X-API-Key: bf_YOUR_KEY'
# → 200 { deleted: true }; the suite run may be submitted againRefusals are named: 409 not_a_suite_run for a bmr_ single-task id, 409 suite_run_not_completed while the job is still running or ended failed/interrupted, 409 suite_run_not_graded when no task was graded, 409 already_submitted (with the existing community_run_id) for a second submit, and 400 invalid_submission for an over-long name or notes. Only the key that started the run can submit or delete; a stranger reads 404. The Run your model panel does all of this from the page: pick a suite, watch it poll, press Publish as a community run, and the board below it refreshes.
CLI
The single-task verb blobfish benchmark run <slug> --model … exists today (Models). The suite and community shapes below land with the CLI lane; until that release is served, use the API calls above — the CLI verbs are thin wrappers over exactly those routes.
$ blobfish benchmark run <slug> --model <model> --suite sample|full [--max-tasks N] # POST …/runs with suite, then polls links.self
$ blobfish benchmark runs <slug> # GET …/runs → runs + suite_runs
$ blobfish benchmark submit <slug> <run_id> [--display-name NAME] # POST …/runs/<run_id>/submitThe honesty boundary
- Hosted tasks vs the official suite. A hosted suite run scores the frozen tasks this sandbox carries with your model — every task graded by the benchmark's own verifier. coverage.official_suite says whether it covered every official task; anything less is a self-run, not the frozen leaderboard. Today each first-party bundle carries one reviewed task of its 100-task release, so
coverage.hosted_tasksis what this host can run andcoverage.official_suiteis false for it. The panel and the board print that from the record; the leaderboard stays the Harbor run of the frozen release. - One job per account. A second suite POST while one is in flight answers
409 run_in_progressnaming the runningrun_id; follow it instead. - Interrupted, not silently running. The runner is in-process. If the process that owned a job is gone or its heartbeat is stale, the job reads
interruptedwitherror.kind: "interrupted"and the tasks graded so far — never as still running, never as a score. Start a new run. - A rejected credential stops the suite. An
authfailure on one task fails the job and the remaining tasks are not attempted; the record says so. - Keys are never stored. The bf_ key and any provider key travel on the request only; records carry the model name and
credential_source(request | account | server), nothing secret. Session evidence expires after 30 minutes; recorded scores do not.