Developers
REST API
This is the proposed contract. The endpoints are not live yet; request and response shapes will be published with them.
https://api.voxelspatiallabs.com/v1Quickstart
Create a design, start a run, poll until it finishes, fetch the bill of materials. Asynchronous by design, because a run takes minutes to hours.
# 1. Create a design from a plain-English description
curl -X POST https://api.voxelspatiallabs.com/v1/designs \
-H "Authorization: Bearer $ANUBIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"spec": "A rover that patrols a two-hectare vegetable farm, photographs every bed each morning and returns to its charger."}'
# -> {"design_id": "..."}
# 2. Start a run
curl -X POST https://api.voxelspatiallabs.com/v1/designs/$DESIGN_ID/runs \
-H "Authorization: Bearer $ANUBIS_API_KEY"
# -> {"run_id": "..."}
# 3. Poll until the run completes or a gate blocks it
curl https://api.voxelspatiallabs.com/v1/runs/$RUN_ID \
-H "Authorization: Bearer $ANUBIS_API_KEY"
# 4. Fetch the bill of materials as CSV
curl "https://api.voxelspatiallabs.com/v1/designs/$DESIGN_ID/bom?format=csv" \
-H "Authorization: Bearer $ANUBIS_API_KEY" -o bom_full.csvimport os
import time
import requests
BASE = "https://api.voxelspatiallabs.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ANUBIS_API_KEY']}"}
# 1. Create a design from a plain-English description
design = requests.post(
f"{BASE}/designs",
headers=HEADERS,
json={"spec": "A rover that patrols a two-hectare vegetable farm, "
"photographs every bed each morning and returns to its charger."},
).json()
design_id = design["design_id"]
# 2. Start a run
run = requests.post(f"{BASE}/designs/{design_id}/runs", headers=HEADERS).json()
run_id = run["run_id"]
# 3. Poll until the run completes or a gate blocks it (runs take minutes to hours)
while True:
status = requests.get(f"{BASE}/runs/{run_id}", headers=HEADERS).json()
if status["state"] in ("completed", "blocked", "failed"):
break
time.sleep(30)
# 4. Fetch the bill of materials
bom = requests.get(f"{BASE}/designs/{design_id}/bom", headers=HEADERS).json()
for line in bom["lines"]:
print(line["part"], line["mpn"], line["quantity"])const BASE = "https://api.voxelspatiallabs.com/v1";
const headers = {
Authorization: `Bearer ${process.env.ANUBIS_API_KEY}`,
"Content-Type": "application/json",
};
// 1. Create a design from a plain-English description
const { design_id } = await fetch(`${BASE}/designs`, {
method: "POST",
headers,
body: JSON.stringify({
spec: "A rover that patrols a two-hectare vegetable farm, photographs every bed each morning and returns to its charger.",
}),
}).then((r) => r.json());
// 2. Start a run
const { run_id } = await fetch(`${BASE}/designs/${design_id}/runs`, { method: "POST", headers }).then((r) => r.json());
// 3. Poll until the run completes or a gate blocks it (runs take minutes to hours)
let status;
do {
await new Promise((resolve) => setTimeout(resolve, 30_000));
status = await fetch(`${BASE}/runs/${run_id}`, { headers }).then((r) => r.json());
} while (!["completed", "blocked", "failed"].includes(status.state));
// 4. Fetch the bill of materials
const bom = await fetch(`${BASE}/designs/${design_id}/bom`, { headers }).then((r) => r.json());
for (const line of bom.lines) console.log(line.part, line.mpn, line.quantity);Field names in the samples follow the proposed contract and may change before the endpoints ship. Prefer the events stream over polling for runs that take hours.
Reference
Every path is relative to https://api.voxelspatiallabs.com/v1. A change is always planned before it is applied, so an agent can show the user what will re-run and what it costs.
| Method | Path | Purpose |
|---|---|---|
| POST | /designs | Create a design from a natural-language spec. Returns a design id. |
| POST | /designs/{id}/runs | Start a run. Returns a run id. |
| GET | /runs/{run_id} | Status per stage and gate. |
| GET | /runs/{run_id}/events | Server-sent events stream for a run. |
| POST | /designs/{id}/changes | Natural-language change. Returns a change plan; nothing is applied. |
| POST | /changes/{change_id}/apply | Apply a change plan. Returns a run id. |
| GET | /designs/{id}/assumptions | Every value Anubis filled in, with its source. |
| GET | /designs/{id}/gates | Gate results with evidence links. |
| GET | /designs/{id}/bom | Bill of materials as JSON, or CSV with ?format=csv. |
| GET | /designs/{id}/artifacts | List artifacts. Filter with ?domain=mechanical|electrical|pcb|... |
| POST | /designs/{id}/release-requests | Start an authorized engineering review. |
Webhooks
Register an HTTPS endpoint per workspace. Each event carries the design id, the run or change id, and a timestamp.
- run.completed
- Every stage that applies has run and its gates have a result.
- run.blocked
- A gate blocked the run. The payload names the gate and the reason.
- change.applied
- A change plan was applied and the affected stages are re-running.
- release.state_changed
- A release request moved between requested, in review, changes requested and released.
Authentication, errors, rate limits and credits
- Authentication
- API keys are issued per workspace and sent as a bearer token. Keys are shown once; rotate them from the workspace.
- Errors
- Errors are JSON with a stable code and a human-readable message. A blocked run is not an error: the run status reports the gate and its reason.
- Rate limits
- Limits apply per key. A limited request gets HTTP 429 with a Retry-After header. The numbers will be published with the endpoints.
- Credits
- Usage is billed in the same credits as the workspace. A change plan reports its estimated cost before anything runs. Manufacturing-format artifacts are released only after authorized engineering review; until then the artifacts endpoint returns review copies.
Prefer tools over raw HTTP? See the MCP server.