The GravForge v1 API turns a plain-language product description into a complete hardware package — architecture, bill of materials, firmware and an assembly guide — and lets you audit any design with two grounded engines: Heliosfor embodied energy & CO₂e, and Argus for product→product upcycle routes. Energy figures and recipes are never invented; they come only from cited reference data and deterministic engines.
From zero to your first forged hardware package in three steps.
gf_ and are shown once — store it server-side.description to /api/v1/forge. You get a 202 back with a forge_id and the URLs to track it.status_url until status is complete, then download artifacts from outputs_url.# 1. Mint a key at https://gravforge.com/dashboard/api-keys
export GF_KEY="gf_your_key_here"
# 2. Enqueue a forge — returns 202 with stream_url + status_url
curl -sS -X POST https://gravforge.com/api/v1/forge \
-H "Authorization: Bearer $GF_KEY" \
-H "Content-Type: application/json" \
-d '{"description":"A battery-powered soil moisture sensor that logs to WiFi every 15 minutes."}'
# -> 202 Accepted
# -> { "ok": true, "version": "1.0", "data": {
# "forge_id": "…", "status": "queued",
# "stream_url": "https://gravforge.com/api/v1/forge/…/stream",
# "status_url": "https://gravforge.com/api/v1/forge/…",
# "outputs_url": "https://gravforge.com/api/v1/forge/…/outputs" } }
# 3a. Watch it build live over SSE (stage | result | complete | error)
curl -N -H "Authorization: Bearer $GF_KEY" \
https://gravforge.com/api/v1/forge/<forge_id>/stream
# 3b. …or just poll until status is "complete"
curl -sS -H "Authorization: Bearer $GF_KEY" \
https://gravforge.com/api/v1/forge/<forge_id>Free accounts run the full pipeline on local (Ollama) inference at 3 forges/month. A failed or canceled forge does not count against your allowance. See pricing for higher tiers.
Every request needs a key. Mint one in your dashboard.
GravForge keys start with gf_. Create and revoke them at /dashboard/api-keys. Send the key one of two ways (a logged-in session cookie also works):
# Header style 1 — Authorization: Bearer
curl -H "Authorization: Bearer gf_your_key_here" https://gravforge.com/api/v1
# Header style 2 — X-API-Key
curl -H "X-API-Key: gf_your_key_here" https://gravforge.com/api/v1Keep keys server-side. Calls are rate-limited per tier and CORS is enabled for browser tooling.
One shape for everything — success or error.
Every endpoint returns the same envelope, so SDKs can rely on a single contract. Check ok first; read data on success or error.code on failure.
// success
{ "ok": true, "version": "1.0", "data": { /* shape depends on endpoint */ },
"meta": { /* optional: paging, usage */ } }
// error
{ "ok": false, "version": "1.0",
"error": { "code": "rate_limited", "message": "Rate limit reached for your tier.",
"status": 429, "retry_after": 60 },
"docs": "https://gravforge.com/developers" }The full v1 surface. Base URL: https://gravforge.com
API discovery index — lists every endpoint and links to the spec.
The OpenAPI 3.1 document (public — no key needed).
Enqueue a product generation. Returns 202 with forge_id, stream_url and status_url.
Poll a forge job: status → processing / complete / error, with result when done.
Server-Sent Events stream of the forge build (stage | result | complete | error).
List downloadable artifacts once a forge is complete — BOM, KiCad, firmware, mechanical and the full package ZIP, each with an honest quality status.
Download one artifact by id (e.g. package_zip, bom_csv) with API-key auth.
GET {factors, constants}; POST a bill or product to compute {profile, bill}.
GET {recipes, presets}; POST an inventory to get {result, inventory}.
The grounded LCA factor list — {materials}, each row cited with source_doc + source_url.
POST → 202 → connect to the stream → stage/result/complete
/api/v1/forge with a description. You get an immediate 202 carrying stream_url and status_url.stream_url with SSE. Events arrive as the six stages run: describe → decompose → specify → bom → firmware → assemble. Each stage event marks progress; result events carry stage output; complete carries the full package; error means it failed.status_url and honour Retry-After until status is complete — then read data.result (brief, architecture, components, bom, firmware, assembly).# 1. Mint a key at https://gravforge.com/dashboard/api-keys
export GF_KEY="gf_your_key_here"
# 2. Enqueue a forge — returns 202 with stream_url + status_url
curl -sS -X POST https://gravforge.com/api/v1/forge \
-H "Authorization: Bearer $GF_KEY" \
-H "Content-Type: application/json" \
-d '{"description":"A battery-powered soil moisture sensor that logs to WiFi every 15 minutes."}'
# -> 202 Accepted
# -> { "ok": true, "version": "1.0", "data": {
# "forge_id": "…", "status": "queued",
# "stream_url": "https://gravforge.com/api/v1/forge/…/stream",
# "status_url": "https://gravforge.com/api/v1/forge/…",
# "outputs_url": "https://gravforge.com/api/v1/forge/…/outputs" } }
# 3a. Watch it build live over SSE (stage | result | complete | error)
curl -N -H "Authorization: Bearer $GF_KEY" \
https://gravforge.com/api/v1/forge/<forge_id>/stream
# 3b. …or just poll until status is "complete"
curl -sS -H "Authorization: Bearer $GF_KEY" \
https://gravforge.com/api/v1/forge/<forge_id>import time, json, requests
KEY = "gf_your_key_here" # mint at /dashboard/api-keys
H = {"Authorization": f"Bearer {KEY}"}
# 1. enqueue
r = requests.post(
"https://gravforge.com/api/v1/forge",
headers={**H, "Content-Type": "application/json"},
json={"description": "A battery-powered soil moisture sensor "
"that logs to WiFi every 15 minutes."},
)
job = r.json()["data"]
forge_id, status_url = job["forge_id"], job["status_url"]
# 2. (option A) stream stages live
with requests.get(job["stream_url"], headers=H, stream=True) as s:
for line in s.iter_lines(decode_unicode=True):
if line.startswith("event:"):
print("event", line.split(":", 1)[1].strip())
elif line.startswith("data:"):
print(json.loads(line[5:]))
# 2. (option B) poll until done
while True:
res = requests.get(status_url, headers=H)
body = res.json()["data"]
if body["status"] == "complete":
print(body["result"]) # brief, architecture, components, bom, firmware, assembly
break
if body["status"] == "error":
raise RuntimeError(body["error"])
time.sleep(int(res.headers.get("Retry-After", "5")))const KEY = "gf_your_key_here"; // mint at /dashboard/api-keys
const H = { Authorization: `Bearer ${KEY}` };
// 1. enqueue
const r = await fetch("https://gravforge.com/api/v1/forge", {
method: "POST",
headers: { ...H, "Content-Type": "application/json" },
body: JSON.stringify({
description:
"A battery-powered soil moisture sensor that logs to WiFi every 15 minutes.",
}),
});
const { data: job } = await r.json();
// 2. stream stages live over SSE
const res = await fetch(job.stream_url, { headers: H });
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
for (const frame of buffer.split("\n\n")) {
const ev = frame.match(/^event: ?(.*)$/m)?.[1];
const dataLine = frame.match(/^data: ?(.*)$/m)?.[1];
if (ev) console.log(ev, dataLine && JSON.parse(dataLine));
}
buffer = buffer.slice(buffer.lastIndexOf("\n\n") + 2);
}Grounded numbers only: the model decomposes, the dataset decides.
Compute the embodied energy and carbon of a design. In mode:"bill" you supply material masses directly. In mode:"product" the model only decomposes your description into a material bill — every energy figure is then looked up in the cited LCA dataset. The response carries the profile (with an honest grade and cited sources) and the bill used.
# Helios — grounded embodied energy. Numbers come ONLY from cited LCA factors;
# the model may decompose a product into a material bill, never invent a figure.
# (A) mode:"bill" — you supply the material masses (fully deterministic)
curl -sS -X POST https://gravforge.com/api/v1/helios \
-H "Authorization: Bearer $GF_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "bill",
"bill": { "basis": "one bracket",
"lines": [ { "material_key": "aluminium_primary", "mass_grams": 120 } ] }
}'
# -> { "ok": true, "version": "1.0", "data": {
# "profile": { "total_embodied_energy_mj": …, "total_kg_co2e": …,
# "grade": "ready", "sources": [ { "source_doc": "…", "source_url": "…" } ] },
# "bill": { "lines": [ … ] } } }
# (B) mode:"product" — the model decomposes the description into a bill, then
# every factor is looked up in the cited dataset (no energy is hallucinated)
curl -sS -X POST https://gravforge.com/api/v1/helios \
-H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
-d '{ "mode": "product", "product": "a 1L stainless steel water bottle" }'Verified recipes matched deterministically against your salvage.
Send a recovered-parts inventory and Argus matches it against the verified recipe corpus — no recipe is invented. Each match reports whether it's satisfied, a grade, and the recipe's source_url + confidence. has_routes is true only when the corpus produced at least one real route.
# Argus — match a recovered-parts inventory against VERIFIED upcycle recipes.
# Nothing is invented: only the verified recipe corpus is searched.
curl -sS -X POST https://gravforge.com/api/v1/argus \
-H "Authorization: Bearer $GF_KEY" \
-H "Content-Type: application/json" \
-d '{
"inventory": [
{ "material_class": "li_ion_18650", "quantity": 6, "mass_grams": 270 },
{ "material_class": "dc_motor", "quantity": 2, "mass_grams": 180 }
]
}'
# -> { "ok": true, "version": "1.0", "data": {
# "result": { "matches": [ { "recipe": { "output_product": "…", "source_url": "…",
# "confidence": "verified" }, "satisfied": true, "grade": "ready" } ],
# "has_routes": true, "grade": "ready" },
# "inventory": [ … normalised … ] } }The dataset that makes Helios figures defensible.
GET /api/v1/materials returns { materials } — the full grounded LCA factor list. Every row carries embodied_energy_mj_per_kg, kg_co2e_per_kg, a real source_doc + source_url, and an honest confidence label (verified / curated / extracted).
curl -sS -H "Authorization: Bearer $GF_KEY" \
https://gravforge.com/api/v1/materialsPredictable failures, machine-readable codes.
Errors return { ok:false, version, error, docs }. The error object always has a stable code, a human message, and the HTTP status (plus extras like retry_after).
On 429, wait the number of seconds in Retry-After before retrying. While a forge is still building, GET /api/v1/forge/{id} returns 202 with its own Retry-After poll hint.
Rate limits are a monthly forge allowance per tier — Free 3, Maker 50, Pro & Enterprise unlimited — the same allowances enforced across the product. Read-only metadata routes (the discovery index and OpenAPI spec) are not metered. The 429 body echoes your current usage, limit and tier. See pricing for the full breakdown.