Requant Lab API

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.

How to call it

Addresshttps://requant.network/lab/api/v1; the version is the first path segment.
BodiesScenarios and edits: application/toml; series, story and command requests: application/json; a story: application/vnd.quantlab.story (Brotli-compressed with Accept-Encoding: br).
API keyOptional: 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 keyX-QuantLab-World-Key, given once when a world is created; without it the world is not found (404).
ErrorsEvery 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.
RetentionResults are kept for a day, idle worlds for 7 days.

TypeScript client

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.

TypeScript
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);
}

Examples

Service health

The queue, the limits and the GPU; no key needed.

curl
curl https://requant.network/lab/api/v1/health
Python
import requests
print(requests.get("https://requant.network/lab/api/v1/health").json())
TypeScript
import { QuantLab } from "./quantlab.ts";
const api = new QuantLab();
console.log(await api.getHealth());

A run of a scenario

The TOML scenario joins the queue; once the run is done, its files can be fetched for a day.

curl
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.json
Python
import 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()
TypeScript
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();

A series of runs

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.

curl
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>/events
Python
import 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)
TypeScript
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 story for the map

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
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/stories
Python
import 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"}
TypeScript
const story = await api.requestStory({ input: readFileSync("scenario.toml", "utf8"), seed: "42" });
const bytes = story instanceof Uint8Array ? story : await api.getStory(story.id);

A world on the server

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
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>/events
Python
import 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})
TypeScript
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 plan from the lab

A network library, as the lab saves it, compiles into a scenario with the same code as in the browser.

curl
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>"}
Python
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"]
TypeScript
const { scenario } = await api.compilePlan({ library: JSON.parse(readFileSync("library.json", "utf8")) });

Reference

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.

health

GET /openapi.json

This contract (OpenAPI 3.1).

Client method: getOpenapi

  • 200 The contract. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json

GET /health

Service state for monitoring: queue, counts, limits.

Client method: getHealth

  • 200 Service state. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json

plans

POST /scenarios/check

Check a scenario and return it normalized, with the engines that take it.

Client method: checkScenario · body: application/toml

  • 200 The scenario as the engine reads it. application/json
  • 400 Not UTF-8 TOML, or not a valid scenario. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Scenario above 32 MiB. application/json

POST /plans/compile

A library's plan as a scenario, as the lab's planner builds it.

Client method: compilePlan · body: application/json

  • 200 The scenario. application/json
  • 400 Bad request, a library of the wrong shape, or a plan problem (with message). application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Library above 1 MB, or a scenario above 32 MiB. application/json

POST /plans/amendment

Some of a plan's cards as an amendment to a world.

Client method: compileAmendment · body: application/json

  • 200 The amendment. application/json
  • 400 Bad request, unknown cards, a library of the wrong shape, or a plan problem (with message). application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Library above 1 MB. application/json

GET /profiles

Real networks scaled for a demo, each with its world as a scenario.

Client method: listProfiles

  • 200 The profiles. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json

GET /presets

The reference scenarios.

Client method: listPresets

  • 200 The presets. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json

runs

POST /runs

Queue 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.

Client method: submitRun · body: application/toml

  • 202 Queued. application/json
  • 400 Not UTF-8 TOML, rejected by the engine, or protocol mode unavailable. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Scenario above 32 MiB. application/json
  • 429 Quota of the address, or of the API key when one is sent (20 runs and series per hour). application/json
  • 503 Queue full (50) or the run cannot be stored. application/json

GET /runs/{id}

Status of a run; a finished one carries its summary and file list.

Client method: getRun

  • id (path) 32 lowercase hex.
  • 200 Status. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 Unknown or expired id (results are kept 24 hours). application/json

GET /runs/{id}/events

Stream 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`.

Client method: streamRunEvents

  • id (path) 32 lowercase hex.
  • 200 Event stream. Each event is `event: job` with a `data:` line holding the status JSON. text/event-stream
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 Unknown or expired id (results are kept 24 hours). application/json
  • 503 Too many event streams, try later. application/json

GET /runs/{id}/{file}

A result file of a finished run, or the bounded digest view.json.

Client method: getRunFile

  • id (path) 32 lowercase hex.
  • file (path) Result file.
  • 200 The file. application/json, text/markdown, application/x-ndjson, text/csv
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 Not finished, expired, unknown file, or file missing. application/json

series

POST /series

Queue 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.

Client method: submitSeries · body: application/json

  • 202 Queued; `run.series` is set. application/json
  • 400 Invalid request, scenario outside the fast engine, or GPU unavailable. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Request above 64 MiB. application/json
  • 429 Quota of the address, or of the API key when one is sent. application/json
  • 503 Queue full or the series cannot be stored. application/json

GET /series/{id}

Status of a series; a finished one carries its report.

Client method: getSeries

  • id (path) 32 lowercase hex.
  • 200 Status. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 Unknown or expired id. application/json

GET /series/{id}/events

Stream 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`.

Client method: streamSeriesEvents

  • id (path) 32 lowercase hex.
  • 200 Event stream. Each event is `event: job` with a `data:` line holding the status JSON. text/event-stream
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 Unknown or expired id (results are kept 24 hours). application/json
  • 503 Too many event streams, try later. application/json

GET /series/{id}/{file}

A result file of a finished series. chain.json exists only for a series requested with `chain`.

Client method: getSeriesFile

  • id (path) 32 lowercase hex.
  • file (path) Result file.
  • 200 The file. application/json, text/markdown
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 Not finished, expired, unknown file, or file missing. application/json

stories

POST /stories

The 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}.

Client method: requestStory · body: application/json

  • 200 The story, format QLSTORY2. application/vnd.quantlab.story
  • 202 Waiting or building. application/json
  • 400 Invalid request, or scenario outside the fast engine (kept 10 minutes). application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Request above the scenario limit. application/json
  • 429 The address, or the API key when one is sent, used its 30 minutes of building an hour. application/json
  • 503 Queue of stories full (500). application/json

GET /stories/{id}

The story once built, otherwise its place or its error. Accept-Encoding: br is honoured as for POST /stories.

Client method: getStory

  • id (path) 64 lowercase hex: SHA-256 of the story kind, seed and scenario.
  • 200 The story, format QLSTORY2. application/vnd.quantlab.story
  • 202 Waiting or building. application/json
  • 400 The build failed. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such story. application/json

GET /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].

Client method: getStorySlice

  • 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.
  • 200 The slice, format QLSTORY2 with a window. application/vnd.quantlab.story
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such story or slice (a story not built yet, or a whole one, has only slice 0). application/json

internal

POST /worker/take

GPU worker: take the oldest series waiting for it. Internal. Needs the worker token in X-QuantLab-Worker. `job` is null when none waits.

Client method: workerTake

  • 200 A series or null. application/json
  • 403 Missing or wrong worker token. application/json

POST /worker/{id}/result

GPU worker: return the report of a series it took. Internal. Needs the worker token in X-QuantLab-Worker.

Client method: workerResult · body: application/json

  • id (path) 32 lowercase hex.
  • 200 Stored. application/json
  • 400 Not {series, markdown} or not the fast series format. application/json
  • 403 Missing or wrong worker token. application/json
  • 404 No such series in progress. application/json
  • 503 Cannot store the series. application/json

POST /worker/{id}/failed

GPU 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).

Client method: workerFailed · body: text/plain

  • id (path) 32 lowercase hex.
  • 200 Stored. application/json
  • 403 Missing or wrong worker token. application/json
  • 404 No such series in progress. application/json

channels

GET /channels

The live channels at this moment.

Client method: listChannels

  • 200 The channels. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json

GET /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.

Client method: getChannel

  • id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.
  • 200 The channel. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such channel. application/json
  • 500 The season's plan does not compile. application/json

POST /channels/{id}/impacts

A 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.

Client method: addChannelImpact · body: application/json

  • id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.
  • 202 The impact and the channel with it. application/json
  • 400 Not the expected request, the impact does not fit the network or the season, or a support of none going on or to come, of one's own, or a second from a name. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such channel. application/json
  • 429 Not enough charges (by default three, one back a minute; a split and hiding blocks cost two), thirty an hour from an address, the season's impacts taken (3000), the queue full (12 waiting), or no place left this season (the channel's limits). application/json
  • 500 The channel could not be saved. application/json

GET /channels/{id}/now

A 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.

Client method: getChannelNow

  • id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.
  • 200 The channel now. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such channel. application/json
  • 500 The season's plan does not compile. application/json

worlds

POST /worlds

Create 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.

Client method: createWorld · body: application/toml

  • 201 Created, paused. application/json
  • 400 Not UTF-8 TOML, rejected by the engine, or protocol mode unavailable. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Scenario above 32 MiB. application/json
  • 429 Quota of the address, or of the API key when one is sent (10 worlds per hour, clones included). application/json
  • 503 Too many worlds on the server or the world cannot be stored. application/json

POST /worlds/realtime/{pace}

Create a real-time world: no horizon, its clock runs at `pace` world seconds per second.

Client method: createRealtimeWorld · body: application/toml

  • pace (path) World seconds per second.
  • 201 Created, paused. application/json
  • 400 Pace outside 1..86400, or the scenario is rejected. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 413 Scenario above 32 MiB. application/json
  • 429 Quota of the address, or of the API key when one is sent. application/json
  • 503 Too many worlds on the server or the world cannot be stored. application/json

GET /worlds/{id}

State of a world.

Client method: getWorld

  • 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).
  • 200 The world. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such world, or a wrong key. application/json

GET /worlds/{id}/events

Stream 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`.

Client method: streamWorldEvents

  • 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).
  • 200 Event stream: `event: world` (WorldEnvelope JSON) and `event: live` (a live.json frame). text/event-stream
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such world, or a wrong key. application/json
  • 503 Too many event streams, try later. application/json

POST /worlds/{id}/commands

start (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.

Client method: commandWorld · body: application/json

  • 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).
  • 200 The world after the command, or the id of a deleted one. application/json
  • 201 A clone (command `clone`). application/json
  • 400 The world's scenario cannot be read back. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such world, or a wrong key. application/json
  • 409 The command is unknown or not available in the world's current state. application/json
  • 429 Quota of the address, or of the API key when one is sent (clone). application/json
  • 503 Cannot carry out the command. application/json

POST /worlds/{id}/amend

Add an amendment (TOML) to a paused world, or to a running real-time one.

Client method: amendWorld · body: application/toml

  • 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).
  • 200 The world with the amendment taken. application/json
  • 400 Not UTF-8, or the amendment does not fit the world at its time. application/json
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such world, or a wrong key. application/json
  • 409 The world is not paused (or a running real-time one), or has 256 amendments. application/json
  • 413 Amendment above 256 KiB. application/json
  • 503 Cannot hand the amendment to the world. application/json

GET /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.

Client method: getWorldFile

  • 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.
  • 200 The file. application/json, text/markdown, application/x-ndjson, text/csv, application/toml
  • 401 Unknown or revoked API key (only when a key is sent). application/json
  • 404 No such world, a wrong key, or the file is not available. application/json

admin

GET /admin/channels

Every channel at once: its settings, who watches, its network now and its season's impacts.

Client method: adminChannels

  • 200 The channels. application/json
  • 403 No admin token, or a wrong one. application/json

PATCH /admin/channels/{id}

Changes some of a channel's settings; a new pace starts a new season.

Client method: adminSetChannel · body: application/json

  • id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.
  • 200 The channel's settings and season. application/json
  • 400 Not settings, or out of their bounds. application/json
  • 403 No admin token, or a wrong one. application/json
  • 404 No such channel. application/json
  • 500 The settings could not be saved. application/json

POST /admin/channels/{id}/restart

Starts a channel's next season now: a new world with no impacts.

Client method: adminRestartChannel

  • id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.
  • 200 The channel's settings and its new season. application/json
  • 403 No admin token, or a wrong one. application/json
  • 404 No such channel. application/json
  • 500 The settings could not be saved. application/json

POST /admin/channels/{id}/network

Gives 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.

Client method: adminSetChannelNetwork · body: application/json

  • id (path) A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.
  • 200 The channel's settings and its new season. application/json
  • 400 Not network settings, too many nodes, or a network whose season does not compile. application/json
  • 403 No admin token, or a wrong one. application/json
  • 404 No such channel. application/json
  • 500 The settings could not be saved. application/json