GET /openapi.json
This contract (OpenAPI 3.1).
- 200 The contract. application/json
- 401 Unknown or revoked API key (only when a key is sent). application/json
The service's HTTP API: runs and series of the blockchain network simulator, world stories for the map, long-lived worlds on the server and the lab's plans. Whatever the lab does is open to programs. The contract is OpenAPI 3.1; this page and the client are generated from it.
| Address | https://requant.network/lab/api/v1; the version is the first path segment. |
|---|---|
| Bodies | Scenarios and edits: application/toml; series, story and command requests: application/json; a story: application/vnd.quantlab.story (Brotli-compressed with Accept-Encoding: br). |
| API key | Optional: Authorization: Bearer qlk_…. With it the hourly quotas count per key, without it per address. The server's owner issues keys; an unknown or revoked key gets 401. |
| World key | X-QuantLab-World-Key, given once when a world is created; without it the world is not found (404). |
| Errors | Every answer outside 2xx is {"error": "…", "status": code}. 400: the body or the scenario failed validation, 404: no such thing, 413: the body is too big, 429: the hourly quota is used up, 503: the service is busy. |
| Retention | Results are kept for a day, idle worlds for 7 days. |
quantlab.ts is one file without dependencies: a method per operation, with the types of bodies and answers; event streams are async iterators. It works in the browser, Node 22+ and Deno. An API error arrives as QuantLabError with its status and the error body.
import { QuantLab, QuantLabError } from "./quantlab.ts";
const api = new QuantLab({ apiKey: process.env.QUANTLAB_KEY });
try {
console.log((await api.listPresets()).presets.map((preset) => preset.id));
} catch (error) {
if (error instanceof QuantLabError) console.error(error.status, error.message);
}The queue, the limits and the GPU; no key needed.
curl https://requant.network/lab/api/v1/healthimport requests
print(requests.get("https://requant.network/lab/api/v1/health").json())import { QuantLab } from "./quantlab.ts";
const api = new QuantLab();
console.log(await api.getHealth());The TOML scenario joins the queue; once the run is done, its files can be fetched for a day.
curl -X POST -H "Content-Type: application/toml" --data-binary @scenario.toml https://requant.network/lab/api/v1/runs
# {"run":{"id":"<id>","status":"queued",...},"position":1}
curl https://requant.network/lab/api/v1/runs/<id>
curl -o summary.json https://requant.network/lab/api/v1/runs/<id>/summary.jsonimport time, requests
api = "https://requant.network/lab/api/v1"
run = requests.post(f"{api}/runs", data=open("scenario.toml", "rb"),
headers={"Content-Type": "application/toml"}).json()["run"]
while run["status"] in ("queued", "running"):
time.sleep(1)
run = requests.get(f"{api}/runs/{run['id']}").json()["run"]
summary = requests.get(f"{api}/runs/{run['id']}/summary.json").json()import { readFileSync } from "node:fs";
import { QuantLab } from "./quantlab.ts";
const api = new QuantLab();
let { run } = await api.submitRun(readFileSync("scenario.toml", "utf8"));
while (run.status === "queued" || run.status === "running") {
await new Promise((done) => setTimeout(done, 1000));
({ run } = await api.getRun(run.id));
}
const summary = await (await api.getRunFile(run.id, "summary.json")).json();The fast engine: up to a million runs of one scenario, or a pair of scenarios on shared seeds. The series can be followed as a stream of events.
jq -n --rawfile s scenario.toml '{scenarios: [$s], runs: 100}' |
curl -X POST -H "Content-Type: application/json" -d @- https://requant.network/lab/api/v1/series
curl -N https://requant.network/lab/api/v1/series/<id>/eventsimport requests
api = "https://requant.network/lab/api/v1"
job = requests.post(f"{api}/series", json={"scenarios": [open("scenario.toml").read()], "runs": 100}).json()["run"]
with requests.get(f"{api}/series/{job['id']}/events", stream=True) as events:
for line in events.iter_lines(decode_unicode=True):
print(line)const { run } = await api.submitSeries({ scenarios: [readFileSync("scenario.toml", "utf8")], runs: 100 });
for await (const event of api.streamSeriesEvents(run.id)) console.log(event.event, JSON.parse(event.data));
const report = await (await api.getSeriesFile(run.id, "series.json")).json();A fast-engine world recorded whole: the lab's map opens it. A small world comes back in the answer, a big one by id while it builds (202).
curl -X POST -H "Content-Type: application/json" -H "Accept-Encoding: br" \
-d '{"input": "<TOML>", "seed": "42"}' -o story.qlstory https://requant.network/lab/api/v1/storiesimport requests
answer = requests.post("https://requant.network/lab/api/v1/stories", json={"input": open("scenario.toml").read(), "seed": "42"})
story = answer.content if answer.status_code == 200 else answer.json() # 202: {"id", "status", "position"}const story = await api.requestStory({ input: readFileSync("scenario.toml", "utf8"), seed: "42" });
const bytes = story instanceof Uint8Array ? story : await api.getStory(story.id);A long-lived world: start it, stop it, edit it as it runs and clone it. The world key is given once, at creation.
curl -X POST -H "Content-Type: application/toml" --data-binary @scenario.toml https://requant.network/lab/api/v1/worlds
# {"world":{"id":"<id>",...},"key":"<key>"}
curl -X POST -H "X-QuantLab-World-Key: <key>" -H "Content-Type: application/json" \
-d '{"command": "start"}' https://requant.network/lab/api/v1/worlds/<id>/commands
curl -N -H "X-QuantLab-World-Key: <key>" https://requant.network/lab/api/v1/worlds/<id>/eventsimport requests
api = "https://requant.network/lab/api/v1"
created = requests.post(f"{api}/worlds", data=open("scenario.toml", "rb"),
headers={"Content-Type": "application/toml"}).json()
world, key = created["world"]["id"], created["key"]
requests.post(f"{api}/worlds/{world}/commands", json={"command": "start"},
headers={"X-QuantLab-World-Key": key})const { world, key } = await api.createWorld(readFileSync("scenario.toml", "utf8"));
await api.commandWorld(world.id, key, { command: "start" });
for await (const event of api.streamWorldEvents(world.id, key)) console.log(event.event);A network library, as the lab saves it, compiles into a scenario with the same code as in the browser.
curl -X POST -H "Content-Type: application/json" -d @request.json https://requant.network/lab/api/v1/plans/compile
# request.json: {"library": <library>, "network": "<id>", "plan": "<id>"}import json, requests
library = json.load(open("library.json"))
scenario = requests.post("https://requant.network/lab/api/v1/plans/compile", json={"library": library}).json()["scenario"]const { scenario } = await api.compilePlan({ library: JSON.parse(readFileSync("library.json", "utf8")) });Every operation of the contract; client methods are named after operationId. Descriptions come from the contract. The worker operations are internal, for the GPU worker.
/openapi.jsonThis contract (OpenAPI 3.1).
/healthService state for monitoring: queue, counts, limits.
/scenarios/checkCheck a scenario and return it normalized, with the engines that take it.
/plans/compileA library's plan as a scenario, as the lab's planner builds it.
/plans/amendmentSome of a plan's cards as an amendment to a world.
/profilesReal networks scaled for a demo, each with its world as a scenario.
/presetsThe reference scenarios.
/runsQueue a run of a TOML scenario. The scenario is validated by the engine. Limits above the server's are reduced and listed in `run.clamped`. Protocol-mode scenarios need the adapter on the server.
/runs/{id}Status of a run; a finished one carries its summary and file list.
id (path) 32 lowercase hex./runs/{id}/eventsStream of status changes of a run (Server-Sent Events). Event `job`: the same JSON as `GET /runs/{id}`, sent at first and whenever it changes. The stream ends after a final status (completed, partial, failed). Server-Sent Events (`text/event-stream`), written straight to the socket with `X-Accel-Buffering: no` and `Connection: close`. A quiet stream sends a `: keep-alive` comment every 15 seconds; a stream lives at most an hour (the client reconnects); at most 64 streams are open at once. Read it with `fetch` when a header must be sent (EventSource cannot send the world key header), or with `curl -N`.
id (path) 32 lowercase hex./runs/{id}/{file}A result file of a finished run, or the bounded digest view.json.
id (path) 32 lowercase hex.file (path) Result file./seriesQueue a series of up to 1 000 000 runs on the fast engine. One scenario, or a pair compared on shared seeds. Scenarios must fit the fast engine. Quota and queue are shared with runs.
/series/{id}Status of a series; a finished one carries its report.
id (path) 32 lowercase hex./series/{id}/eventsStream of status changes of a series (Server-Sent Events). Event `job`: the same JSON as `GET /series/{id}`, sent at first and whenever it changes. The stream ends after a final status (completed, partial, failed). Server-Sent Events (`text/event-stream`), written straight to the socket with `X-Accel-Buffering: no` and `Connection: close`. A quiet stream sends a `: keep-alive` comment every 15 seconds; a stream lives at most an hour (the client reconnects); at most 64 streams are open at once. Read it with `fetch` when a header must be sent (EventSource cannot send the world key header), or with `curl -N`.
id (path) 32 lowercase hex./series/{id}/{file}A result file of a finished series. chain.json exists only for a series requested with `chain`.
id (path) 32 lowercase hex.file (path) Result file./storiesThe story of a world for the map: from the cache, built within the request, or a place in the queue. Send `Accept-Encoding: br` to receive the stored Brotli body as is (`Content-Encoding: br`); otherwise it is unpacked. A world built within 2 seconds comes back in this response, otherwise 202 and poll GET /stories/{id}.
/stories/{id}The story once built, otherwise its place or its error. Accept-Encoding: br is honoured as for POST /stories.
id (path) 64 lowercase hex: SHA-256 of the story kind, seed and scenario./stories/{id}/slices/{index}A window of a big story: a story of its own from one keyframe to a later one (roadmap И.3). A story above the server's slice size comes in slices: POST /stories and GET /stories/{id} give slice 0; its header names the story, every slice's moments (cuts) and the whole run's overview. Slice n plays the story from cuts[n] to cuts[n+1].
id (path) 64 lowercase hex: SHA-256 of the story kind, seed and scenario.index (path) Slice number, 0 the first, without sign or leading zero./worker/takeGPU worker: take the oldest series waiting for it. Internal. Needs the worker token in X-QuantLab-Worker. `job` is null when none waits.
/worker/{id}/resultGPU worker: return the report of a series it took. Internal. Needs the worker token in X-QuantLab-Worker.
id (path) 32 lowercase hex./worker/{id}/failedGPU worker: report that a series it took failed. Internal. Needs the worker token in X-QuantLab-Worker. The body is the error text (the first 600 characters are kept).
id (path) 32 lowercase hex./channelsThe live channels at this moment.
/channels/{id}A channel at this moment: its clock, its season's plan and who took each impact, who watches, and the scenario to build its story.
id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd./channels/{id}/impactsA viewer's impact joins the channel's plan from its time plus a lead: a split, nodes off, a delay, a pool hiding its blocks, a pool's surge, lost messages; impacts going on at once add up.
id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd./channels/{id}/nowA channel's clock and audience at this moment, without its plan: a tab asks for this often and for the whole channel when its version changes.
id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd./worldsCreate a world from a TOML scenario; it starts paused. The response carries the world's `key`, shown once. Later requests send it as X-QuantLab-World-Key.
/worlds/realtime/{pace}Create a real-time world: no horizon, its clock runs at `pace` world seconds per second.
pace (path) World seconds per second./worlds/{id}State of a world.
id (path) 32 lowercase hex.X-QuantLab-World-Key (header) The world's key from its creation. A wrong key looks like a missing world (404)./worlds/{id}/eventsStream of a world's state and live frames (Server-Sent Events). Event `world`: the same JSON as `GET /worlds/{id}`, sent at first and whenever it changes. For a running real-time world, event `live` with each new frame (the `live.json` body). The stream ends after a final world status (completed, stopped, limited, failed). Server-Sent Events (`text/event-stream`), written straight to the socket with `X-Accel-Buffering: no` and `Connection: close`. A quiet stream sends a `: keep-alive` comment every 15 seconds; a stream lives at most an hour (the client reconnects); at most 64 streams are open at once. Read it with `fetch` when a header must be sent (EventSource cannot send the world key header), or with `curl -N`.
id (path) 32 lowercase hex.X-QuantLab-World-Key (header) The world's key from its creation. A wrong key looks like a missing world (404)./worlds/{id}/commandsstart (also continue), pause, stop, clone or delete a world. start: paused to queued. pause: queued to paused, running to pausing. stop: from any live state. clone: from a world that is not running; answers 201 with a new world and its key. delete: removes the world.
id (path) 32 lowercase hex.X-QuantLab-World-Key (header) The world's key from its creation. A wrong key looks like a missing world (404)./worlds/{id}/amendAdd an amendment (TOML) to a paused world, or to a running real-time one.
id (path) 32 lowercase hex.X-QuantLab-World-Key (header) The world's key from its creation. A wrong key looks like a missing world (404)./worlds/{id}/{file}A file of a world: scenario, snapshot, amendments, live frame, digest or result files. scenario.toml is always there. Result files and view.json appear once a segment reached a result; snapshot.json after the first snapshot; amendments.json once there are amendments; live.json only for a running real-time world. `files` in the world lists what exists now.
id (path) 32 lowercase hex.X-QuantLab-World-Key (header) The world's key from its creation. A wrong key looks like a missing world (404).file (path) World file./admin/channelsEvery channel at once: its settings, who watches, its network now and its season's impacts.
/admin/channels/{id}Changes some of a channel's settings; a new pace starts a new season.
id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd./admin/channels/{id}/restartStarts a channel's next season now: a new world with no impacts.
id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd./admin/channels/{id}/networkGives a channel a network of its own (the planner's network settings: nodes, pools, miners, latency, difficulty), or null for its profile's; its next season starts now on it.
id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.