Blobfish API v1 · /api/v1/sandbox
Generate, run, and verify synthetic RL worlds over HTTP.
This is the same API the Blobfish sandboxruns on. One prompt generates a relational, executable world — SQLite tables, Python tools, grounded tasks, and deterministic VCode verifiers — hosted at a stable worldId. Then drive its tasks with an agent and score the real state changes. No LLM judge in the reward path.
https://blobfish.aiGetting Started
Generate a world, act in it, and score the result in eight steps. Commands run against https://blobfish.ai. Endpoints that depend on external infrastructure (RunPod deploy, Composio research, GPU training) return an explicit not_configured status when unavailable. Get a free API key for higher rate limits and world ownership, or try anonymously (10 generations/day).
Get an API Key (optional)
Anonymous access works for exploration (10 gen/day). For sustained use, get a free key — gives 10 gen/day and scoped world ownership.
POST /api/v1/auth/keyscurl -sX POST https://blobfish.ai/api/v1/auth/keys \
-H 'Content-Type: application/json' \
-d '{"name":"my-key","email":"you@example.com"}'
# Returns: { "key": "bf_626c4b...", "tier": "free", "rate_limits": {...} }
# Then pass either header on all subsequent requests:
# -H 'X-API-Key: bf_626c4b...'
# -H 'Authorization: Bearer bf_626c4b...'Generate a World
Send a domain prompt and get back a full executable world with tables, tools, tasks, and verifiers.
POST /api/v1/sandbox/jobscurl -sX POST https://blobfish.ai/api/v1/sandbox/jobs \
-H 'Content-Type: application/json' \
-d '{"prompt":"law firm matter intake operations"}'
# Returns: { "job_id": "job_e78af19041f046eb", "status": "creating_world" }Poll Until Ready
Poll the job endpoint or connect to the SSE stream. Every job terminates in exactly one of "ready" or "failed" (with job.error naming the failing stage) — a server-side watchdog fails any job with no progress for 8 minutes, so no job builds forever.
GET /api/v1/sandbox/jobs/{jobId}# Option A: Poll
curl -s https://blobfish.ai/api/v1/sandbox/jobs/job_e78af19041f046eb
# Returns: { "job": { "status": "ready", "world_id": "sbx_..." } }
# On failure: { "job": { "status": "failed", "error": "Stage \"...\" failed: ..." } }
# Option B: SSE stream
curl -sN https://blobfish.ai/api/v1/sandbox/jobs/job_e78af19041f046eb/stream
# data: {"type":"progress","phase":"research","detail":"Discovering tools..."}
# data: {"type":"done","status":"ready","world_id":"sbx_..."}Inspect the World
Retrieve the full world: thesis, SQLite tables with seeded rows, executable Python tools, grounded tasks, and VCode verifiers.
GET /api/v1/sandbox/worlds/{worldId}curl -s https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc \
| python3 -m json.tool | head -40
# thesis.company, thesis.domain, thesis.roles
# tables[].name, tables[].columns, tables[].sample_rows
# tools[].name, tools[].type, tools[].source
# tasks[].task_id, tasks[].prompt, tasks[].required_tools
# verifiers[].task_id, verifiers[].assertions, verifiers[].vcodeRun an Agent Task
An agent drives a task against live SQLite. The VCode verifier scores real before/after state and emits a binary reward.
POST /api/v1/sandbox/worlds/{worldId}/run-taskcurl -sX POST https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc/run-task \
-H 'Content-Type: application/json' \
-d '{"task_id":"task_001"}'
# Returns: { "passed": true, "reward": 1, "runtime": "local",
# "steps": [...], "verifier": { "passed": true, "assertions": [...] } }Check Quality
Audit the world for degenerate tasks, grounding coverage, difficulty distribution, and creation-calibration rollout explanations. Quality is a hard gate, not a report: degenerate rewards, missing research coverage, Quick Preview provenance, and out-of-band calibration block benchmarks, MCP-Mark, and queue-for-training (422) until fixed.
GET /api/v1/sandbox/worlds/{worldId}/qualitycurl -s https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc/quality
# Returns: { "quality": { "degenerate_tasks": 0, "total_tasks": 14,
# "grounding_coverage": { "coverage": 0.833 },
# "difficulty": { "easy": 4, "medium": 6, "hard": 4 } },
# "validity": { "training_ready": true, "blockers": [] },
# "creation_calibration": { "summary": {...}, "tasks": [{"pass_rate": 0.5, "variance": {...}}] } }Download Harbor Package
Download the self-contained tar archive with schema, tools, verifiers, tasks, Dockerfile, and recorded traces.
GET /api/v1/sandbox/worlds/{worldId}/downloadcurl -s https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc/download \
-o world.tar
tar xf world.tar -C my-world/
# world.json, server.py, create_db.py, tools.py, DockerfileDeploy & Run
Build the Harbor image locally or deploy to RunPod for hosted evaluation.
POST /api/v1/sandbox/worlds/{worldId}/deploy# Option A: Run locally
cd my-world && docker build -t world . && docker run -p 8080:8080 world
# Option B: Deploy to RunPod
curl -sX POST https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc/deploy
# Returns: { "status": "deployed", "url": "https://api.runpod.ai/v2/ep_abc" }Endpoints
39 endpoints grouped into 7 categories. Click Try it on any endpoint to send a live request from this page.
Create executable RL worlds from a natural-language prompt.
/api/v1/sandbox/generateSynchronous Quick Preview generation for anchored prototypes. Blocks until the world is ready and returns it directly, but stamps the result as quick_preview_ungrounded_prototype and not training-ready. Requires PRD/API/SOP anchor_files; use POST /api/v1/sandbox/jobs with mode=deep for production quality gates. Send {"stream":true} for SSE progress events.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Domain description, e.g. "law firm matter intake" |
anchor_files | array | Yes | Uploaded anchor documents with filename/content, e.g. PRD markdown or OpenAPI JSON |
stream | boolean | No | If true, returns a Server-Sent-Events stream of generation stages instead of blocking |
Response
{
"world_id": "sbx_9f3aa4bc6f684ff3",
"thesis": {
"company": "Meridian Legal Partners",
"domain": "legal",
"vertical": "legal_services",
"roles": [
"partner",
"associate",
"paralegal"
]
},
"tables": [
{
"name": "matters",
"columns": [
"id",
"title",
"status",
"assigned_to"
],
"row_count": 12
}
],
"tools": [
{
"name": "open_matter",
"type": "write",
"target_tables": [
"matters"
]
}
],
"tasks": [
{
"task_id": "task_001",
"prompt": "Open matter MAT-2024-0031 and assign to associate Chen",
"required_tools": [
"open_matter"
]
}
],
"verifiers": [
{
"task_id": "task_001",
"assertions": [
"matters_mat_2024_0031_status_is_open",
"matters_mat_2024_0031_assigned_to_is_chen",
"no_collateral_matters",
"reads_before_writes"
]
}
]
}curl
curl -sX POST https://blobfish.ai/api/v1/sandbox/generate \
-H 'Content-Type: application/json' \
-d '{"prompt":"law firm matter intake operations","anchor_files":[{"filename":"matter-intake-prd.md","content":"# Matters Schema\n- title: string\n- status: enum open closed\n# Clients Schema\n- name: string\n- email: string"}]}'/api/v1/sandbox/jobsAsync job-based generation. Sandbox jobs always use the Research-backed staged path: the job must execute research, collect evidence sources, pass creation scorecard gates, and satisfy a creation-time complexity check before it is marked ready. This step-budget proxy is not model calibration; benchmark and training use require measured model rollouts. Returns a job_id immediately while the world builds in the background.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Domain description for world generation |
mode | string | No | Ignored by sandbox job creation; jobs always run Research-backed staged generation. |
target_failure_rate | number | No | Desired baseline failure rate, e.g. 0.5 for maximum training signal |
anchors | array | No | Uploaded docs, URLs, PRDs, API specs, or database schemas used as grounding evidence |
Response
{
"job_id": "job_e78af19041f046eb",
"status": "creating_world",
"stages": [
{
"key": "create_world",
"label": "Creating world",
"status": "running"
},
{
"key": "make_package",
"label": "Making executable package",
"status": "pending"
},
{
"key": "launch_sandbox",
"label": "Launching sandbox",
"status": "pending"
},
{
"key": "run_first_test",
"label": "Running first agent test",
"status": "pending"
}
]
}curl
curl -sX POST https://blobfish.ai/api/v1/sandbox/jobs \
-H 'Content-Type: application/json' \
-d '{"prompt":"law firm matter intake operations","mode":"deep","target_failure_rate":0.5}'/api/v1/sandbox/jobs/{jobId}Poll a build job. While running returns live stage progress; once status is "ready" the response includes the full world. TERMINAL-STATE CONTRACT: every job ends in exactly one of "ready" or "failed" — never an infinite build. On "failed", job.error names the failing stage and reason (world-generation, package, sandbox-launch, first-test, or validation failure). A server-side watchdog reconciles jobs with no progress for 8 minutes to "failed" (generation process died), so a job can never stay non-terminal forever.
Response
{
"job": {
"job_id": "job_e78af19041f046eb",
"status": "ready",
"world_id": "sbx_9f3aa4bc6f684ff3",
"world": {
"thesis": {
"company": "Meridian Legal Partners",
"domain": "legal"
},
"tables": [
{
"name": "matters",
"columns": [
"id",
"title",
"status"
]
}
],
"tools": [
{
"name": "open_matter",
"type": "write"
}
],
"tasks": [
{
"task_id": "task_001",
"prompt": "..."
}
]
}
}
}curl
curl -s https://blobfish.ai/api/v1/sandbox/jobs/job_e78af19041f046eb/api/v1/sandbox/jobs/{jobId}/streamSSE progress stream for a build job. Emits stage transitions, pipeline phase progress, thesis previews, and a terminal done/error event.
Response
data: {"type":"stage","stage":"creating_world"}
data: {"type":"progress","stage":"create_world","phase":"research","detail":"Discovering MCP tools...","iteration":1,"maxIterations":3}
data: {"type":"thesis_preview","thesis":{"company":"Meridian Legal","domain":"legal"}}
data: {"type":"done","status":"ready","world_id":"sbx_9f3aa4bc6f684ff3"}
# Failure is equally terminal — never an infinite stream:
# data: {"type":"done","status":"failed","error":"Stage \"Creating world\" failed: ..."}
# data: {"type":"timeout"} (stream safety-closes after 5 minutes)curl
curl -sN https://blobfish.ai/api/v1/sandbox/jobs/job_e78af19041f046eb/stream/api/v1/sandbox/jobsList recent build jobs for your session (identified by HttpOnly cookie).
Response
{
"jobs": [
{
"job_id": "job_e78af19041f046eb",
"status": "ready",
"world_id": "sbx_9f3aa4bc6f684ff3",
"prompt": "law firm matter intake"
},
{
"job_id": "job_a1b2c3d4e5f60718",
"status": "creating_world",
"prompt": "dental clinic scheduling"
}
]
}curl
curl -s https://blobfish.ai/api/v1/sandbox/jobs/api/v1/sandbox/worlds/importImport an externally built world. Two modes: (1) JSON body (Content-Type: application/json) imports a SandboxWorld-shaped payload into the sandbox runtime. Imported JSON worlds are stamped as app-mirror/replay imports and are not training-ready; use /api/v1/sandbox/jobs mode=deep for Research-backed training worlds. (2) Multipart tar.gz (Content-Type: multipart/form-data) imports a Blobfish download package into the hosted world store for evaluation, chat, and run-task workflows. Use ?target=hosted or ?target=sandbox to choose the store (defaults: JSON=sandbox, multipart=hosted). The archive must contain world.json, tools.json, tasks.json and optionally environment.db, schema.sql, seed.sql, personas.json, env_spec.json. Max 24 MB JSON / 64 MB tar.gz.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
tables | array | Yes | [{name, columns:[{name,type,pk?,fk?}], sample_rows:[...]}] -- rows seed the live SQLite (JSON mode) |
tools | array | Yes | [{name, type, source, parameters, target_tables}] -- source must define `def <name>(db_path, ...)` (JSON mode) |
tasks | array | No | [{task_id, prompt, required_tools, ...}] -- extra provenance fields (expected_calls) ride through (JSON mode) |
verifiers | array | No | [{task_id, vcode}] -- vcode must define verify(initial_state, final_state, trace); every task needs one (JSON mode) |
thesis | object | No | {company, domain, vertical, ...} shown in the workspace header (JSON mode) |
world | file | No | tar.gz archive of the mirror world directory (multipart mode) |
label | string | No | Human-readable label for the imported world (multipart mode) |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
target | string | No | 'hosted' or 'sandbox' -- which store receives the import (default: sandbox for JSON, hosted for multipart) |
Response
{
"world_id": "sbx_1767ddbd5dcd",
"url": "/sandbox?world=sbx_1767ddbd5dcd",
"target": "sandbox",
"counts": {
"tables": 36,
"rows": 745,
"tools": 113,
"tasks": 11,
"verifiers": 11
},
"warnings": []
}curl
# JSON import (sandbox):
curl -sX POST https://blobfish.ai/api/v1/sandbox/worlds/import \
-H 'Content-Type: application/json' --data-binary @world_payload.json
# tar.gz import (hosted world store):
tar czf world.tar.gz -C ./blobfish_worlds env_myapp_mirror_x
curl -sX POST https://blobfish.ai/api/v1/sandbox/worlds/import \
-H 'X-API-Key: bf_yourkey' \
-F world=@world.tar.gz -F label="My App Mirror"
# JSON import into hosted store:
curl -sX POST "https://blobfish.ai/api/v1/sandbox/worlds/import?target=hosted" \
-H 'Content-Type: application/json' --data-binary @world_payload.jsonNotes
Authentication
Anonymous access works for exploration (10 gen/day, 60 calls/hour). For production use, create a free API key via POST /api/v1/auth/keys and pass it as X-API-Key header or Authorization: Bearer bf_... header. Free tier: 10 gen/day, 100 calls/hour. Worlds created with a key are tracked and retrievable via ?mine=true.
LLM-Optional
Generation is prompt-driven and LLM-optional: with a provider key the domain model is richer, otherwise a deterministic heuristic pipeline still produces a runnable world.
Persistence
Worlds persist per server instance. Set BLOBFISH_SANDBOX_DIR to a durable mount for cross-instance access. Worlds are also downloadable as Harbor archives.
Verification
Every task is scored by executable VCode over real before/after SQLite state. No LLM judge in the reward path. Verifiers use deterministic state-diff assertions.
Honesty Policy
The API honestly reports its capabilities. Benchmarks with no in-repo harness return null. RunPod returns not_configured when unavailable. GRPO regressions are disclosed.
Rate Limits
Anonymous: 10 gen/day, 60 API calls/hour. Free key: 10 gen/day, 100 calls/hour. Pro: 100/1000. Enterprise: 1000/10000. Exceeded? 429 with Retry-After header.
Error Responses
All errors return JSON with an error field. Common: 404 world not found,429 rate limit exceeded,400 missing required fields,500 generation timeout or VCode execution error.
Cookbook: Attach your production agent over HTTP
Create a key, fork an isolated session for the mirrored sandbox world, then point any MCP-over-HTTP client at the returned mcp_url. Pass both the API key andX-Blobfish-Session on every call so repeated or parallel runs do not share state. For assessment and CI runs, also pass X-Blobfish-Task,X-Blobfish-Run, and X-Request-Id so the report can attribute every tool call to a task.
- MCP over HTTP: use the returned
mcp_url. - Blobfish JSON transport: use the returned
tool_calls_url. - OpenAI-compatible agent endpoint: use
blobfish eval --policy agent. - Vendor/internal REST base-URL swapping: use
tools/vendor_rest_shimwith a route map.
# 1. Create an API key
curl -sX POST https://blobfish.ai/api/v1/auth/keys \
-H 'Content-Type: application/json' \
-d '{"name":"ci-agent","email":"agent@example.com"}'
# 2. Create a session for one sandbox world
curl -sX POST https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc6f684ff3/sessions \
-H 'X-API-Key: bf_YOUR_KEY'
# → { "session_id": "sess_...", "mcp_url": ".../mcp", "tool_calls_url": ".../tool-calls" }
# 3. Use HTTP MCP against the session fork
curl -sX POST https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc6f684ff3/mcp \
-H 'X-API-Key: bf_YOUR_KEY' \
-H 'X-Blobfish-Session: sess_...' \
-H 'X-Blobfish-Task: task_...' \
-H 'X-Blobfish-Run: ci-2026-07-08' \
-H 'X-Blobfish-Run-Mode: ci' \
-H 'X-Request-Id: req_001' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'Reset between runs with POST /api/v1/sandbox/worlds/<world_id>/sessions/<session_id>/reset. For REST-only agents, run python tools/vendor_rest_shim/vendor_rest_shim.py --config route-map.jsonand point the vendor base URL at the shim.
Cookbook: Regression-test your agent in CI
Use the Blobfish CLI to gate PRs on agent quality. Three commands:
# 1. Export your world's tasks as an eval dataset
blobfish export-eval ./my_world --format jsonl
# → eval.jsonl: {task_id, split, difficulty, instruction, expected_calls, verifier_ref}
# 2. Grade your agent (or use oracle/random baselines)
blobfish eval ./my_world --policy agent --agent-endpoint http://localhost:3000/api/agent --run v1
# 3. Gate on regressions (exit 1 = fail the build)
blobfish ci-gate ./my_world \
--policy agent \
--agent-endpoint http://localhost:3000/api/agent \
--hosted-world-url https://blobfish.ai/api/v1/sandbox/worlds/sbx_9f3aa4bc6f684ff3 \
--api-key bf_YOUR_KEY \
--min-pass-rate 0.7 \
--no-regressions \
--baseline baseline.json \
--write-baseline current-baseline.json \
--langfuse-dataset blobfish-pr \
--langfuse-run "$GITHUB_SHA"Optional: push eval datasets to Langfuse with --format langfuse (requiresLANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY). Omit --hosted-world-url to run the same agent loop against the local CLI harness instead of hosted sandbox sessions. Use the Blobfish download package workflow for moving a generated world into the hosted sandbox, and app-mirror for cloning your production app into a testable mock world.
Cookbook: Cheaper-model ROI POC
Use validated customer traces for SFT first, then serve the adapter, run A/B eval, and compute cost from measured usage. Training result fields stay nulluntil a real eval measures them.
blobfish distill-data \
--traces ./traces.jsonl \
--world ./my_world \
--out ./sft.jsonl \
--validate replay \
--report-out ./distill_report.json
blobfish train \
--world ./my_world \
--data ./sft.jsonl \
--base Qwen/Qwen3-8B \
--method sft \
--target local-mlx \
--out ./output/train
blobfish serve-adapter \
--adapter ./output/train/adapter \
--base Qwen/Qwen3-8B \
--target local \
--registry-url https://model-registry.example.com
blobfish eval-ab \
--world ./my_world \
--baseline-run frontier \
--agent-endpoint http://127.0.0.1:8000/v1/chat/completions
blobfish cost-report \
--world ./my_world \
--baseline-run frontier \
--eval-run agent-... \
--frontier-price 5.00 \
--tuned-serving-cost 0.30 \
--training-cost 25.00 \
--usage-jsonl ./usage.jsonl