{
  "openapi": "3.1.0",
  "info": {
    "title": "ReQuant Lab API",
    "version": "1",
    "description": "HTTP API of the ReQuant Lab service: server-side runs and series of the discrete-event blockchain simulator, world stories for the map, and long-lived worlds that can be paused, amended, cloned and run in real time. Requests and responses are JSON unless stated otherwise; scenarios are TOML. Result files follow the JSON Schemas in schemas/. Every error has the same shape (Error). Results are kept 24 hours, idle worlds 7 days. Limits apply per client address, or per API key when one is sent in `Authorization: Bearer <key>` (optional; an unknown or revoked key is 401)."
  },
  "servers": [
    {
      "url": "https://requant.network/lab/api/v1"
    }
  ],
  "tags": [
    {
      "name": "health",
      "description": "Service state and this contract."
    },
    {
      "name": "plans",
      "description": "What the lab does before a run: the scenario check, a library's plan as a scenario or a world amendment, network profiles and reference scenarios (crates/lab-plan, the same code the lab runs as WebAssembly)."
    },
    {
      "name": "runs",
      "description": "Single runs of a scenario."
    },
    {
      "name": "series",
      "description": "Series of runs on the fast engine."
    },
    {
      "name": "stories",
      "description": "Binary world stories for the map."
    },
    {
      "name": "worlds",
      "description": "Long-lived worlds, protected by a per-world key."
    },
    {
      "name": "channels",
      "description": "Live channels: real networks played by the clock as a broadcast, with impacts from their viewers (architecture/live-channels-v1.md)."
    },
    {
      "name": "internal",
      "description": "GPU worker endpoints. They need the worker token and are not for clients."
    },
    {
      "name": "admin",
      "description": "The live channels' settings for their owner: bots, limits, pace and new seasons."
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "tags": [
          "health"
        ],
        "operationId": "getOpenapi",
        "summary": "This contract (OpenAPI 3.1).",
        "responses": {
          "200": {
            "description": "The contract.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/health": {
      "get": {
        "tags": [
          "health"
        ],
        "operationId": "getHealth",
        "summary": "Service state for monitoring: queue, counts, limits.",
        "responses": {
          "200": {
            "description": "Service state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/scenarios/check": {
      "post": {
        "tags": [
          "plans"
        ],
        "operationId": "checkScenario",
        "summary": "Check a scenario and return it normalized, with the engines that take it.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "TOML scenario, up to 32 MiB.",
          "content": {
            "application/toml": {
              "schema": {
                "type": "string",
                "maxLength": 33554432
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scenario as the engine reads it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScenarioCheck"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not UTF-8 TOML, or not a valid scenario."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Scenario above 32 MiB."
          }
        }
      }
    },
    "/plans/compile": {
      "post": {
        "tags": [
          "plans"
        ],
        "operationId": "compilePlan",
        "summary": "A library's plan as a scenario, as the lab's planner builds it.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, up to 1 MB of library.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scenario.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompiledPlan"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Bad request, a library of the wrong shape, or a plan problem (with message)."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Library above 1 MB, or a scenario above 32 MiB."
          }
        }
      }
    },
    "/plans/amendment": {
      "post": {
        "tags": [
          "plans"
        ],
        "operationId": "compileAmendment",
        "summary": "Some of a plan's cards as an amendment to a world.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON, up to 1 MB of library.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The amendment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanAmendment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Bad request, unknown cards, a library of the wrong shape, or a plan problem (with message)."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Library above 1 MB."
          }
        }
      }
    },
    "/profiles": {
      "get": {
        "tags": [
          "plans"
        ],
        "operationId": "listProfiles",
        "summary": "Real networks scaled for a demo, each with its world as a scenario.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The profiles.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profiles"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          }
        }
      }
    },
    "/presets": {
      "get": {
        "tags": [
          "plans"
        ],
        "operationId": "listPresets",
        "summary": "The reference scenarios.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The presets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Presets"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          }
        }
      }
    },
    "/runs": {
      "post": {
        "tags": [
          "runs"
        ],
        "operationId": "submitRun",
        "summary": "Queue a run of a TOML scenario.",
        "description": "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.",
        "requestBody": {
          "required": true,
          "description": "TOML scenario, up to 32 MiB.",
          "content": {
            "application/toml": {
              "schema": {
                "type": "string",
                "maxLength": 33554432
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunQueued"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not UTF-8 TOML, rejected by the engine, or protocol mode unavailable."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Scenario above 32 MiB."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "Quota of the address, or of the API key when one is sent (20 runs and series per hour)."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Queue full (50) or the run cannot be stored."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/runs/{id}": {
      "get": {
        "tags": [
          "runs"
        ],
        "operationId": "getRun",
        "summary": "Status of a run; a finished one carries its summary and file list.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "Status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or expired id (results are kept 24 hours)."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/runs/{id}/events": {
      "get": {
        "tags": [
          "runs"
        ],
        "operationId": "streamRunEvents",
        "summary": "Stream of status changes of a run (Server-Sent Events).",
        "description": "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`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Event stream. Each event is `event: job` with a `data:` line holding the status JSON.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "`event: job`, `data:` RunStatus JSON, blank line."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or expired id (results are kept 24 hours)."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Too many event streams, try later."
          }
        }
      }
    },
    "/runs/{id}/{file}": {
      "get": {
        "tags": [
          "runs"
        ],
        "operationId": "getRunFile",
        "summary": "A result file of a finished run, or the bounded digest view.json.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "name": "file",
            "in": "path",
            "required": true,
            "description": "Result file.",
            "schema": {
              "enum": [
                "manifest.json",
                "summary.json",
                "scenario.json",
                "report.md",
                "events.jsonl",
                "blocks.jsonl",
                "metrics.csv",
                "view.json"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/manifest"
                    },
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/summary"
                    },
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/scenario"
                    },
                    {
                      "$ref": "#/components/schemas/ResultView"
                    }
                  ]
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "One JSON object per line: events.jsonl records match schemas/v1 $defs/event, blocks.jsonl match $defs/block."
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "Header line, then one row per sample."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "Not finished, expired, unknown file, or file missing."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/series": {
      "post": {
        "tags": [
          "series"
        ],
        "operationId": "submitSeries",
        "summary": "Queue a series of up to 1 000 000 runs on the fast engine.",
        "description": "One scenario, or a pair compared on shared seeds. Scenarios must fit the fast engine. Quota and queue are shared with runs.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeriesRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued; `run.series` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunQueued"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Invalid request, scenario outside the fast engine, or GPU unavailable."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Request above 64 MiB."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "Quota of the address, or of the API key when one is sent."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Queue full or the series cannot be stored."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/series/{id}": {
      "get": {
        "tags": [
          "series"
        ],
        "operationId": "getSeries",
        "summary": "Status of a series; a finished one carries its report.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "Status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or expired id."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/series/{id}/events": {
      "get": {
        "tags": [
          "series"
        ],
        "operationId": "streamSeriesEvents",
        "summary": "Stream of status changes of a series (Server-Sent Events).",
        "description": "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`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Event stream. Each event is `event: job` with a `data:` line holding the status JSON.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "`event: job`, `data:` RunStatus JSON, blank line."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or expired id (results are kept 24 hours)."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Too many event streams, try later."
          }
        }
      }
    },
    "/series/{id}/{file}": {
      "get": {
        "tags": [
          "series"
        ],
        "operationId": "getSeriesFile",
        "summary": "A result file of a finished series.",
        "description": "chain.json exists only for a series requested with `chain`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "name": "file",
            "in": "path",
            "required": true,
            "description": "Result file.",
            "schema": {
              "enum": [
                "series.json",
                "series.md",
                "chain.json"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/series_fast"
                    },
                    {
                      "type": "object",
                      "description": "chain.json, format quantlab-chain-fast-v1."
                    }
                  ]
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "Not finished, expired, unknown file, or file missing."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/stories": {
      "post": {
        "tags": [
          "stories"
        ],
        "operationId": "requestStory",
        "summary": "The story of a world for the map: from the cache, built within the request, or a place in the queue.",
        "description": "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}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StoryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The story, format QLSTORY2.",
            "headers": {
              "Content-Encoding": {
                "schema": {
                  "const": "br"
                },
                "description": "Present when the client accepts Brotli."
              }
            },
            "content": {
              "application/vnd.quantlab.story": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "description": "Waiting or building.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoryPending"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Invalid request, or scenario outside the fast engine (kept 10 minutes)."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Request above the scenario limit."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "The address, or the API key when one is sent, used its 30 minutes of building an hour."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Queue of stories full (500)."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/stories/{id}": {
      "get": {
        "tags": [
          "stories"
        ],
        "operationId": "getStory",
        "summary": "The story once built, otherwise its place or its error.",
        "description": "Accept-Encoding: br is honoured as for POST /stories.",
        "parameters": [
          {
            "$ref": "#/components/parameters/StoryId"
          }
        ],
        "responses": {
          "200": {
            "description": "The story, format QLSTORY2.",
            "headers": {
              "Content-Encoding": {
                "schema": {
                  "const": "br"
                },
                "description": "Present when the client accepts Brotli."
              }
            },
            "content": {
              "application/vnd.quantlab.story": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "description": "Waiting or building.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StoryPending"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "The build failed."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such story."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/stories/{id}/slices/{index}": {
      "get": {
        "tags": [
          "stories"
        ],
        "operationId": "getStorySlice",
        "summary": "A window of a big story: a story of its own from one keyframe to a later one (roadmap И.3).",
        "description": "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].",
        "parameters": [
          {
            "$ref": "#/components/parameters/StoryId"
          },
          {
            "name": "index",
            "in": "path",
            "required": true,
            "description": "Slice number, 0 the first, without sign or leading zero.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The slice, format QLSTORY2 with a window.",
            "headers": {
              "Content-Encoding": {
                "schema": {
                  "const": "br"
                },
                "description": "Present when the client accepts Brotli."
              }
            },
            "content": {
              "application/vnd.quantlab.story": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such story or slice (a story not built yet, or a whole one, has only slice 0)."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/worker/take": {
      "post": {
        "tags": [
          "internal"
        ],
        "operationId": "workerTake",
        "summary": "GPU worker: take the oldest series waiting for it.",
        "description": "Internal. Needs the worker token in X-QuantLab-Worker. `job` is null when none waits.",
        "security": [
          {
            "WorkerToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "A series or null.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkerJob"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "Missing or wrong worker token."
          }
        }
      }
    },
    "/worker/{id}/result": {
      "post": {
        "tags": [
          "internal"
        ],
        "operationId": "workerResult",
        "summary": "GPU worker: return the report of a series it took.",
        "description": "Internal. Needs the worker token in X-QuantLab-Worker.",
        "security": [
          {
            "WorkerToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkerResult"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "stored"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not {series, markdown} or not the fast series format."
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "Missing or wrong worker token."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such series in progress."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Cannot store the series."
          }
        }
      }
    },
    "/worker/{id}/failed": {
      "post": {
        "tags": [
          "internal"
        ],
        "operationId": "workerFailed",
        "summary": "GPU worker: report that a series it took failed.",
        "description": "Internal. Needs the worker token in X-QuantLab-Worker. The body is the error text (the first 600 characters are kept).",
        "security": [
          {
            "WorkerToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "text/plain": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "stored"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "Missing or wrong worker token."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such series in progress."
          }
        }
      }
    },
    "/channels": {
      "get": {
        "tags": [
          "channels"
        ],
        "operationId": "listChannels",
        "summary": "The live channels at this moment.",
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The channels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channels"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          }
        }
      }
    },
    "/channels/{id}": {
      "get": {
        "tags": [
          "channels"
        ],
        "operationId": "getChannel",
        "summary": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such channel."
          },
          "500": {
            "$ref": "#/components/responses/Error",
            "description": "The season's plan does not compile."
          }
        }
      }
    },
    "/channels/{id}/impacts": {
      "post": {
        "tags": [
          "channels"
        ],
        "operationId": "addChannelImpact",
        "summary": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelImpactRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The impact and the channel with it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelImpact"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "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."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such channel."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "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)."
          },
          "500": {
            "$ref": "#/components/responses/Error",
            "description": "The channel could not be saved."
          }
        }
      }
    },
    "/worlds": {
      "post": {
        "tags": [
          "worlds"
        ],
        "operationId": "createWorld",
        "summary": "Create a world from a TOML scenario; it starts paused.",
        "description": "The response carries the world's `key`, shown once. Later requests send it as X-QuantLab-World-Key.",
        "requestBody": {
          "required": true,
          "description": "TOML scenario, up to 32 MiB.",
          "content": {
            "application/toml": {
              "schema": {
                "type": "string",
                "maxLength": 33554432
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, paused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorldCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not UTF-8 TOML, rejected by the engine, or protocol mode unavailable."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Scenario above 32 MiB."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "Quota of the address, or of the API key when one is sent (10 worlds per hour, clones included)."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Too many worlds on the server or the world cannot be stored."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/worlds/realtime/{pace}": {
      "post": {
        "tags": [
          "worlds"
        ],
        "operationId": "createRealtimeWorld",
        "summary": "Create a real-time world: no horizon, its clock runs at `pace` world seconds per second.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Pace"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "TOML scenario, up to 32 MiB; its duration is replaced by the engine's ten years.",
          "content": {
            "application/toml": {
              "schema": {
                "type": "string",
                "maxLength": 33554432
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, paused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorldCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Pace outside 1..86400, or the scenario is rejected."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Scenario above 32 MiB."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "Quota of the address, or of the API key when one is sent."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Too many worlds on the server or the world cannot be stored."
          }
        },
        "security": [
          {},
          {
            "ApiKey": []
          }
        ]
      }
    },
    "/worlds/{id}": {
      "get": {
        "tags": [
          "worlds"
        ],
        "operationId": "getWorld",
        "summary": "State of a world.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "$ref": "#/components/parameters/WorldKey"
          }
        ],
        "security": [
          {
            "WorldKey": []
          },
          {
            "WorldKey": [],
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The world.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorldEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such world, or a wrong key."
          }
        }
      }
    },
    "/worlds/{id}/events": {
      "get": {
        "tags": [
          "worlds"
        ],
        "operationId": "streamWorldEvents",
        "summary": "Stream of a world's state and live frames (Server-Sent Events).",
        "description": "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`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "$ref": "#/components/parameters/WorldKey"
          }
        ],
        "security": [
          {
            "WorldKey": []
          },
          {
            "WorldKey": [],
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Event stream: `event: world` (WorldEnvelope JSON) and `event: live` (a live.json frame).",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "`event: world`, `data:` WorldEnvelope JSON; `event: live`, `data:` the live.json frame; each followed by a blank line."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such world, or a wrong key."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Too many event streams, try later."
          }
        }
      }
    },
    "/worlds/{id}/commands": {
      "post": {
        "tags": [
          "worlds"
        ],
        "operationId": "commandWorld",
        "summary": "start (also continue), pause, stop, clone or delete a world.",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "$ref": "#/components/parameters/WorldKey"
          }
        ],
        "security": [
          {
            "WorldKey": []
          },
          {
            "WorldKey": [],
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorldCommand"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The world after the command, or the id of a deleted one.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/WorldEnvelope"
                    },
                    {
                      "$ref": "#/components/schemas/WorldDeleted"
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "A clone (command `clone`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorldCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "The world's scenario cannot be read back."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such world, or a wrong key."
          },
          "409": {
            "$ref": "#/components/responses/Error",
            "description": "The command is unknown or not available in the world's current state."
          },
          "429": {
            "$ref": "#/components/responses/Error",
            "description": "Quota of the address, or of the API key when one is sent (clone)."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Cannot carry out the command."
          }
        }
      }
    },
    "/worlds/{id}/amend": {
      "post": {
        "tags": [
          "worlds"
        ],
        "operationId": "amendWorld",
        "summary": "Add an amendment (TOML) to a paused world, or to a running real-time one.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "$ref": "#/components/parameters/WorldKey"
          }
        ],
        "security": [
          {
            "WorldKey": []
          },
          {
            "WorldKey": [],
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "TOML amendment, up to 256 KiB.",
          "content": {
            "application/toml": {
              "schema": {
                "type": "string",
                "maxLength": 262144
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The world with the amendment taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorldEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not UTF-8, or the amendment does not fit the world at its time."
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such world, or a wrong key."
          },
          "409": {
            "$ref": "#/components/responses/Error",
            "description": "The world is not paused (or a running real-time one), or has 256 amendments."
          },
          "413": {
            "$ref": "#/components/responses/Error",
            "description": "Amendment above 256 KiB."
          },
          "503": {
            "$ref": "#/components/responses/Error",
            "description": "Cannot hand the amendment to the world."
          }
        }
      }
    },
    "/worlds/{id}/{file}": {
      "get": {
        "tags": [
          "worlds"
        ],
        "operationId": "getWorldFile",
        "summary": "A file of a world: scenario, snapshot, amendments, live frame, digest or result files.",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          },
          {
            "$ref": "#/components/parameters/WorldKey"
          },
          {
            "name": "file",
            "in": "path",
            "required": true,
            "description": "World file.",
            "schema": {
              "enum": [
                "scenario.toml",
                "snapshot.json",
                "amendments.json",
                "live.json",
                "manifest.json",
                "summary.json",
                "scenario.json",
                "report.md",
                "events.jsonl",
                "blocks.jsonl",
                "metrics.csv",
                "view.json"
              ]
            }
          }
        ],
        "security": [
          {
            "WorldKey": []
          },
          {
            "WorldKey": [],
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/manifest"
                    },
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/summary"
                    },
                    {
                      "$ref": "https://quantlab.local/schemas/v1#/$defs/scenario"
                    },
                    {
                      "$ref": "#/components/schemas/ResultView"
                    },
                    {
                      "$ref": "#/components/schemas/Amendments"
                    },
                    {
                      "$ref": "https://quantlab.local/schemas/cli-snapshot-v1"
                    },
                    {
                      "type": "object",
                      "description": "live.json: the latest frame of a real-time world."
                    }
                  ]
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string",
                  "description": "One JSON object per line: events.jsonl records match schemas/v1 $defs/event, blocks.jsonl match $defs/block."
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "Header line, then one row per sample."
                }
              },
              "application/toml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such world, a wrong key, or the file is not available."
          }
        }
      }
    },
    "/channels/{id}/now": {
      "get": {
        "tags": [
          "channels"
        ],
        "operationId": "getChannelNow",
        "summary": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "security": [
          {},
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The channel now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelNow"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error",
            "description": "Unknown or revoked API key (only when a key is sent)."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such channel."
          },
          "500": {
            "$ref": "#/components/responses/Error",
            "description": "The season's plan does not compile."
          }
        }
      }
    },
    "/admin/channels": {
      "get": {
        "tags": [
          "admin"
        ],
        "operationId": "adminChannels",
        "summary": "Every channel at once: its settings, who watches, its network now and its season's impacts.",
        "security": [
          {
            "AdminToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The channels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminChannels"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "No admin token, or a wrong one."
          }
        }
      }
    },
    "/admin/channels/{id}": {
      "patch": {
        "tags": [
          "admin"
        ],
        "operationId": "adminSetChannel",
        "summary": "Changes some of a channel's settings; a new pace starts a new season.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "security": [
          {
            "AdminToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelSettingsChange"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The channel's settings and season.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminChannelSet"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not settings, or out of their bounds."
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "No admin token, or a wrong one."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such channel."
          },
          "500": {
            "$ref": "#/components/responses/Error",
            "description": "The settings could not be saved."
          }
        }
      }
    },
    "/admin/channels/{id}/restart": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "adminRestartChannel",
        "summary": "Starts a channel's next season now: a new world with no impacts.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "security": [
          {
            "AdminToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The channel's settings and its new season.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminChannelSet"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "No admin token, or a wrong one."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such channel."
          },
          "500": {
            "$ref": "#/components/responses/Error",
            "description": "The settings could not be saved."
          }
        }
      }
    },
    "/admin/channels/{id}/network": {
      "post": {
        "tags": [
          "admin"
        ],
        "operationId": "adminSetChannelNetwork",
        "summary": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ChannelId"
          }
        ],
        "security": [
          {
            "AdminToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "description": "The planner's network settings, as GET /channels/{id} gives them in network.settings (the seed is the season's), or null. At most 500 nodes; a season of it must compile.",
                "oneOf": [
                  {
                    "type": "object"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The channel's settings and its new season.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminChannelSet"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error",
            "description": "Not network settings, too many nodes, or a network whose season does not compile."
          },
          "403": {
            "$ref": "#/components/responses/Error",
            "description": "No admin token, or a wrong one."
          },
          "404": {
            "$ref": "#/components/responses/Error",
            "description": "No such channel."
          },
          "500": {
            "$ref": "#/components/responses/Error",
            "description": "The settings could not be saved."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "qlk_…",
        "description": "Optional. Issued by the server owner with `quantlab key create`. A request with a key is counted and limited under the key instead of the client address; without one the address quotas apply. An unknown or revoked key is rejected with 401, a revoke takes effect at once."
      },
      "WorldKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-QuantLab-World-Key",
        "description": "Key returned once when a world is created."
      },
      "WorkerToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-QuantLab-Worker",
        "description": "Token of the GPU worker. Internal."
      },
      "AdminToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-QuantLab-Admin",
        "description": "The channels' admin token: the text of `<data>/channels/admin-token`, 32 hex digits the server makes on its first start."
      }
    },
    "parameters": {
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "32 lowercase hex.",
        "schema": {
          "$ref": "#/components/schemas/Id"
        }
      },
      "StoryId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "64 lowercase hex: SHA-256 of the story kind, seed and scenario.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        }
      },
      "Pace": {
        "name": "pace",
        "in": "path",
        "required": true,
        "description": "World seconds per second.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 86400
        }
      },
      "WorldKey": {
        "name": "X-QuantLab-World-Key",
        "in": "header",
        "required": true,
        "description": "The world's key from its creation. A wrong key looks like a missing world (404).",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{32}$"
        }
      },
      "ChannelId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "A channel: bitcoin, bitcoin-30, bitcoin-50 or crowd.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z-]{1,32}$"
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "status"
        ],
        "additionalProperties": false,
        "description": "The one error shape of the API.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "description": "The HTTP status again."
          },
          "message": {
            "$ref": "#/components/schemas/PlanMessage",
            "description": "Plan problems only: the lab's dictionary key with its parameters, to show the problem in another language."
          }
        }
      },
      "Id": {
        "type": "string",
        "pattern": "^[0-9a-f]{32}$",
        "description": "128 random bits, 32 lowercase hex."
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "version",
          "engine",
          "queued",
          "running",
          "pool",
          "stored",
          "completed",
          "failed",
          "limits",
          "worlds",
          "stories",
          "gpu"
        ],
        "properties": {
          "status": {
            "const": "ok"
          },
          "version": {
            "type": "string"
          },
          "engine": {
            "type": "string"
          },
          "queued": {
            "type": "integer",
            "minimum": 0,
            "description": "Runs and series waiting in the shared queue."
          },
          "running": {
            "type": "integer",
            "minimum": 0,
            "description": "Runs and series being computed."
          },
          "pool": {
            "type": "object",
            "properties": {
              "cores": {
                "type": "integer",
                "minimum": 0
              },
              "used": {
                "type": "integer",
                "minimum": 0
              }
            },
            "description": "Cores of the one queue and cores in use."
          },
          "stored": {
            "type": "integer",
            "minimum": 0,
            "description": "Run and series jobs kept."
          },
          "completed": {
            "type": "integer",
            "minimum": 0
          },
          "failed": {
            "type": "integer",
            "minimum": 0
          },
          "limits": {
            "type": "object",
            "properties": {
              "queue": {
                "type": "integer",
                "minimum": 0
              },
              "per_ip_hour": {
                "type": "integer",
                "minimum": 0
              },
              "max_events": {
                "type": "integer",
                "minimum": 0
              },
              "max_wall_seconds": {
                "type": "integer",
                "minimum": 0
              },
              "max_output_mib": {
                "type": "integer",
                "minimum": 0
              },
              "keep_seconds": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "worlds": {
            "type": "object",
            "properties": {
              "stored": {
                "type": "integer",
                "minimum": 0
              },
              "running": {
                "type": "integer",
                "minimum": 0
              },
              "queued": {
                "type": "integer",
                "minimum": 0
              },
              "limits": {
                "type": "object",
                "properties": {
                  "max_worlds": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "max_running": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "max_realtime": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "per_ip_hour": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "idle_seconds": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          },
          "stories": {
            "type": "object",
            "properties": {
              "waiting": {
                "type": "integer",
                "minimum": 0
              },
              "building": {
                "type": "integer",
                "minimum": 0
              },
              "seconds_per_hour": {
                "type": "integer",
                "minimum": 0
              },
              "queue": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "gpu": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "queued": {
                "type": "integer",
                "minimum": 0
              },
              "worker_seen": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Unix seconds of the worker's last request."
              }
            }
          }
        }
      },
      "SeriesInfo": {
        "type": "object",
        "required": [
          "runs",
          "first_seed",
          "scenarios"
        ],
        "properties": {
          "runs": {
            "type": "integer",
            "minimum": 0
          },
          "first_seed": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "maxLength": 20
          },
          "scenarios": {
            "type": "integer",
            "enum": [
              1,
              2
            ]
          },
          "gpu": {
            "type": "boolean",
            "description": "Computed by a GPU worker."
          },
          "cpu_instead": {
            "enum": [
              "small",
              "no-worker"
            ],
            "description": "GPU asked for but computed on the server's CPUs."
          },
          "chain": {
            "type": "boolean",
            "description": "A single run with the observer's chain (chain.json)."
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "id",
          "status",
          "name",
          "nodes",
          "horizon_seconds",
          "submitted_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Id"
          },
          "status": {
            "enum": [
              "queued",
              "running",
              "completed",
              "partial",
              "failed"
            ]
          },
          "name": {
            "type": "string"
          },
          "nodes": {
            "type": "integer",
            "minimum": 0
          },
          "horizon_seconds": {
            "type": "integer",
            "minimum": 0
          },
          "submitted_at": {
            "type": "integer",
            "minimum": 0,
            "description": "Unix seconds."
          },
          "started_at": {
            "type": "integer",
            "minimum": 0
          },
          "finished_at": {
            "type": "integer",
            "minimum": 0
          },
          "clamped": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scenario limits reduced to the server's, as `name = value`."
          },
          "error": {
            "type": "string"
          },
          "series": {
            "$ref": "#/components/schemas/SeriesInfo"
          }
        }
      },
      "RunQueued": {
        "type": "object",
        "required": [
          "run",
          "position"
        ],
        "properties": {
          "run": {
            "$ref": "#/components/schemas/Job"
          },
          "position": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Place in the queue, 1 is next."
          }
        }
      },
      "RunStatus": {
        "type": "object",
        "required": [
          "run",
          "position",
          "used",
          "cores"
        ],
        "properties": {
          "run": {
            "$ref": "#/components/schemas/Job"
          },
          "position": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Place in the queue, 1 is next; null once started."
          },
          "used": {
            "type": "integer",
            "minimum": 0,
            "description": "Cores of the shared queue in use."
          },
          "cores": {
            "type": "integer",
            "minimum": 0
          },
          "artifacts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Files to fetch; present for a completed or partial job."
          },
          "summary": {
            "$ref": "https://quantlab.local/schemas/v1#/$defs/summary",
            "description": "Run result summary (summary.json), runs only."
          },
          "report": {
            "$ref": "https://quantlab.local/schemas/v1#/$defs/series_fast",
            "description": "Series report (series.json), series only."
          }
        }
      },
      "ResultView": {
        "type": "object",
        "description": "Bounded digest of a result: totals, the tail of the block tree, thinned metrics, the latest events.",
        "required": [
          "manifest",
          "summary",
          "scenario",
          "blocks",
          "blocks_total",
          "records",
          "records_total",
          "metrics"
        ],
        "properties": {
          "manifest": {
            "type": "object",
            "properties": {
              "status": {
                "$ref": "https://quantlab.local/schemas/v1#/$defs/status"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "profile": {
                "type": "string"
              },
              "seed": {
                "type": "string",
                "pattern": "^(0|[1-9][0-9]*)$",
                "maxLength": 20
              },
              "artifacts_complete": {
                "type": "boolean"
              },
              "history_blake3": {
                "$ref": "https://quantlab.local/schemas/v1#/$defs/hash"
              },
              "state_sha256": {
                "$ref": "https://quantlab.local/schemas/v1#/$defs/hash"
              },
              "configuration_sha256": {
                "$ref": "https://quantlab.local/schemas/v1#/$defs/hash"
              },
              "wall_elapsed_ms": {
                "type": "integer",
                "minimum": 0
              },
              "version": {
                "type": "string"
              }
            }
          },
          "summary": {
            "$ref": "https://quantlab.local/schemas/v1#/$defs/summary"
          },
          "scenario": {
            "$ref": "https://quantlab.local/schemas/v1#/$defs/scenario"
          },
          "blocks": {
            "type": "array",
            "maxItems": 120,
            "items": {
              "$ref": "https://quantlab.local/schemas/v1#/$defs/block"
            },
            "description": "The last 120 blocks."
          },
          "blocks_total": {
            "type": "integer",
            "minimum": 0
          },
          "records": {
            "type": "array",
            "maxItems": 1000,
            "items": {
              "$ref": "https://quantlab.local/schemas/v1#/$defs/event"
            },
            "description": "The last 1000 journal records."
          },
          "records_total": {
            "type": "integer",
            "minimum": 0
          },
          "metrics": {
            "type": "object",
            "required": [
              "columns",
              "rows",
              "total"
            ],
            "properties": {
              "columns": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "rows": {
                "type": "array",
                "maxItems": 1001,
                "items": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "total": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "SeriesRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "scenarios",
          "runs"
        ],
        "properties": {
          "scenarios": {
            "type": "array",
            "minItems": 1,
            "maxItems": 2,
            "items": {
              "type": "string"
            },
            "description": "One TOML scenario, or two compared on shared seeds."
          },
          "runs": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000
          },
          "first_seed": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "maxLength": 20,
            "description": "Default: the first scenario's seed."
          },
          "device": {
            "enum": [
              "cpu",
              "gpu"
            ],
            "default": "cpu",
            "description": "gpu needs a GPU worker enabled on the server (else 400)."
          },
          "chain": {
            "type": "boolean",
            "default": false,
            "description": "One run of one scenario on the server's CPU with the observer's chain."
          }
        }
      },
      "StoryRequest": {
        "type": "object",
        "required": [
          "input",
          "seed"
        ],
        "properties": {
          "input": {
            "type": "string",
            "description": "TOML scenario."
          },
          "seed": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "maxLength": 20
          },
          "tab": {
            "type": "string",
            "maxLength": 64,
            "description": "Asking tab: its newer request replaces its older waiting ones."
          }
        }
      },
      "StoryPending": {
        "type": "object",
        "required": [
          "id",
          "status",
          "position"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "status": {
            "enum": [
              "waiting",
              "building"
            ]
          },
          "position": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "WorldStatus": {
        "enum": [
          "queued",
          "running",
          "pausing",
          "paused",
          "completed",
          "stopped",
          "limited",
          "failed"
        ]
      },
      "World": {
        "type": "object",
        "required": [
          "id",
          "name",
          "profile",
          "nodes",
          "horizon_seconds",
          "created_at",
          "updated_at",
          "status",
          "reason",
          "segments",
          "files",
          "amendments",
          "pending"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Id"
          },
          "name": {
            "type": "string"
          },
          "profile": {
            "type": "string"
          },
          "nodes": {
            "type": "integer",
            "minimum": 0
          },
          "horizon_seconds": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "type": "integer",
            "minimum": 0
          },
          "updated_at": {
            "type": "integer",
            "minimum": 0
          },
          "status": {
            "$ref": "#/components/schemas/WorldStatus"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "segments": {
            "type": "integer",
            "minimum": 0
          },
          "files": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Files available at GET /worlds/{id}/{file}."
          },
          "amendments": {
            "type": "integer",
            "minimum": 0,
            "description": "Amendments applied."
          },
          "pending": {
            "type": "integer",
            "minimum": 0,
            "description": "Amendments waiting to be applied."
          },
          "pace": {
            "type": "integer",
            "minimum": 1,
            "maximum": 86400,
            "description": "Real-time world only: world seconds per second."
          },
          "progress": {
            "type": "object",
            "description": "Latest progress of the computation."
          },
          "clamped": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "parent": {
            "$ref": "#/components/schemas/Id"
          }
        }
      },
      "WorldEnvelope": {
        "type": "object",
        "required": [
          "world"
        ],
        "properties": {
          "world": {
            "$ref": "#/components/schemas/World"
          }
        }
      },
      "WorldCreated": {
        "type": "object",
        "required": [
          "world",
          "key"
        ],
        "properties": {
          "world": {
            "$ref": "#/components/schemas/World"
          },
          "key": {
            "type": "string",
            "pattern": "^[0-9a-f]{32}$",
            "description": "Secret of the world, shown once: send it as X-QuantLab-World-Key."
          }
        }
      },
      "WorldDeleted": {
        "type": "object",
        "required": [
          "deleted"
        ],
        "properties": {
          "deleted": {
            "$ref": "#/components/schemas/Id"
          }
        }
      },
      "WorldCommand": {
        "type": "object",
        "required": [
          "command"
        ],
        "properties": {
          "command": {
            "enum": [
              "start",
              "pause",
              "stop",
              "clone",
              "delete"
            ]
          }
        }
      },
      "Amendments": {
        "type": "object",
        "required": [
          "amendments"
        ],
        "properties": {
          "amendments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "at_event",
                "toml"
              ],
              "properties": {
                "at_event": {
                  "type": "integer",
                  "minimum": 0
                },
                "toml": {
                  "type": "string"
                },
                "pending": {
                  "const": true
                }
              }
            }
          }
        }
      },
      "WorkerJob": {
        "type": "object",
        "required": [
          "job"
        ],
        "properties": {
          "job": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "runs",
                  "first_seed",
                  "scenarios"
                ],
                "properties": {
                  "id": {
                    "$ref": "#/components/schemas/Id"
                  },
                  "runs": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "first_seed": {
                    "type": "string",
                    "pattern": "^(0|[1-9][0-9]*)$",
                    "maxLength": 20
                  },
                  "scenarios": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            ]
          }
        }
      },
      "WorkerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "series",
          "markdown"
        ],
        "properties": {
          "series": {
            "$ref": "https://quantlab.local/schemas/v1#/$defs/series_fast",
            "description": "Series report in the fast series format."
          },
          "markdown": {
            "type": "string"
          }
        }
      },
      "PlanMessage": {
        "type": "object",
        "required": [
          "key"
        ],
        "additionalProperties": false,
        "description": "A key of the lab's dictionary (apps/lab/src/i18n) and the values of its {placeholders}: a number, a text, {count} (a number shown with digit grouping), a nested message, or a list of messages joined by commas.",
        "properties": {
          "key": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "number"
                },
                {
                  "type": "string"
                },
                {
                  "type": "object",
                  "required": [
                    "count"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "count": {
                      "type": "number"
                    }
                  }
                },
                {
                  "$ref": "#/components/schemas/PlanMessage"
                },
                {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PlanMessage"
                  }
                }
              ]
            }
          }
        }
      },
      "PlanRequest": {
        "type": "object",
        "required": [
          "library"
        ],
        "additionalProperties": false,
        "properties": {
          "library": {
            "type": "object",
            "description": "A network library as the lab saves and exports it (schemas/network-library-v1.schema.json), up to 1 MB."
          },
          "network": {
            "type": "string",
            "description": "The network's id; without it, the library's first network."
          },
          "plan": {
            "type": "string",
            "description": "The plan's id in that network; without it, the network's first plan."
          },
          "impacts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Amendment only: ids of the plan's cards to add to a world."
          },
          "prefix": {
            "type": "string",
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9_-]*$",
            "description": "Amendment only: put before the cards' change ids, to keep them apart from the world's earlier changes (the lab uses a1-, a2-…)."
          }
        }
      },
      "CompiledPlan": {
        "type": "object",
        "required": [
          "network",
          "plan",
          "scenario",
          "fast_engine"
        ],
        "additionalProperties": false,
        "properties": {
          "network": {
            "type": "string"
          },
          "plan": {
            "type": "string"
          },
          "scenario": {
            "type": "string",
            "description": "The scenario TOML, for POST /runs, /worlds or /series."
          },
          "fast_engine": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why a series of this world cannot run on the fast engine, or null."
          }
        }
      },
      "PlanAmendment": {
        "type": "object",
        "required": [
          "network",
          "plan",
          "amendment"
        ],
        "additionalProperties": false,
        "properties": {
          "network": {
            "type": "string"
          },
          "plan": {
            "type": "string"
          },
          "amendment": {
            "type": "string",
            "description": "Amendment TOML, for POST /worlds/{id}/amend."
          }
        }
      },
      "ScenarioCheck": {
        "type": "object",
        "required": [
          "scenario",
          "protocol",
          "engines"
        ],
        "additionalProperties": false,
        "properties": {
          "scenario": {
            "$ref": "https://quantlab.local/schemas/v1#/$defs/scenario",
            "description": "The normalized scenario, as scenario.json of a result."
          },
          "protocol": {
            "type": "boolean",
            "description": "Protocol mode: runs only where the server has the Requant adapter."
          },
          "engines": {
            "type": "object",
            "required": [
              "main",
              "fast",
              "fast_issue"
            ],
            "additionalProperties": false,
            "properties": {
              "main": {
                "type": "boolean",
                "description": "The main engine takes it: runs and worlds (up to 128 nodes)."
              },
              "fast": {
                "type": "boolean",
                "description": "The fast engine takes it: series of up to 200 000 nodes."
              },
              "fast_issue": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Why the fast engine does not, or null."
              }
            }
          }
        }
      },
      "Profiles": {
        "type": "object",
        "required": [
          "profiles"
        ],
        "additionalProperties": false,
        "properties": {
          "profiles": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "profile",
                "scenario"
              ],
              "additionalProperties": false,
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "profile": {
                  "type": "object",
                  "description": "Block interval, difficulty rule and window, coin, transaction flow, recommended days, uncles (architecture/network-profiles-v1.md)."
                },
                "scenario": {
                  "type": "string",
                  "description": "The profile's world over its recommended days, as TOML."
                }
              }
            }
          }
        }
      },
      "Presets": {
        "type": "object",
        "required": [
          "presets"
        ],
        "additionalProperties": false,
        "properties": {
          "presets": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "file",
                "name",
                "description",
                "protocol",
                "scenario"
              ],
              "additionalProperties": false,
              "properties": {
                "id": {
                  "type": "string"
                },
                "file": {
                  "type": "string",
                  "description": "Its file in scenarios/."
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "protocol": {
                  "type": "boolean",
                  "description": "Protocol mode (Requant): server only."
                },
                "scenario": {
                  "type": "string",
                  "description": "TOML."
                }
              }
            }
          }
        }
      },
      "ChannelSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "profile",
          "pace",
          "season",
          "started_ms",
          "ends_ms",
          "server_ms",
          "now_us",
          "version",
          "viewers",
          "bots",
          "crowd"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "profile": {
            "type": "string",
            "description": "The network profile (GET /profiles)."
          },
          "pace": {
            "type": "integer",
            "minimum": 1,
            "description": "World seconds per wall second."
          },
          "season": {
            "type": "integer",
            "minimum": 0,
            "description": "The season's number: by default wall time over six hours since the Unix epoch; an admin's restart starts the next one at once. Each season the world starts anew."
          },
          "started_ms": {
            "type": "integer",
            "minimum": 0
          },
          "ends_ms": {
            "type": "integer",
            "minimum": 0
          },
          "server_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "The server's clock when it answered."
          },
          "now_us": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "The channel's world time: wall time since the season's start times the pace."
          },
          "version": {
            "type": "integer",
            "minimum": 0,
            "description": "Impacts in the season's plan: a new one means a new story."
          },
          "viewers": {
            "type": "integer",
            "minimum": 0,
            "description": "Addresses that asked for the channel in the last 30 seconds, and its bots."
          },
          "bots": {
            "type": "integer",
            "minimum": 0,
            "description": "Viewers the server keeps: they watch and step in as people would, through the same limits; counted in viewers."
          },
          "crowd": {
            "type": "boolean",
            "description": "The crowd's channel: a network with many bots stepping in, to see what a large audience does to it."
          }
        }
      },
      "Channel": {
        "type": "object",
        "required": [
          "id",
          "name",
          "profile",
          "pace",
          "season",
          "started_ms",
          "ends_ms",
          "server_ms",
          "now_us",
          "version",
          "viewers",
          "seed",
          "input",
          "network",
          "plan",
          "limits",
          "bots",
          "crowd",
          "you",
          "audience",
          "authors",
          "supporters",
          "supported",
          "charges",
          "asked"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "profile": {
            "type": "string",
            "description": "The network profile (GET /profiles)."
          },
          "pace": {
            "type": "integer",
            "minimum": 1,
            "description": "World seconds per wall second."
          },
          "season": {
            "type": "integer",
            "minimum": 0,
            "description": "The season's number: by default wall time over six hours since the Unix epoch; an admin's restart starts the next one at once. Each season the world starts anew."
          },
          "started_ms": {
            "type": "integer",
            "minimum": 0
          },
          "ends_ms": {
            "type": "integer",
            "minimum": 0
          },
          "server_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "The server's clock when it answered."
          },
          "now_us": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "The channel's world time: wall time since the season's start times the pace."
          },
          "version": {
            "type": "integer",
            "minimum": 0,
            "description": "Impacts in the season's plan: a new one means a new story."
          },
          "viewers": {
            "type": "integer",
            "minimum": 0,
            "description": "Addresses that asked for the channel in the last 30 seconds, and its bots."
          },
          "seed": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "The season's seed, for POST /stories."
          },
          "input": {
            "type": "string",
            "description": "The season's scenario (TOML) with the viewers' impacts composed: the network's state with all of them going on at once; for POST /stories."
          },
          "network": {
            "type": "object",
            "description": "The channel's network in the planner's library format."
          },
          "plan": {
            "type": "object",
            "description": "The season's plan in the planner's library format: its impacts are the viewers'."
          },
          "limits": {
            "type": "object",
            "description": "The impacts viewers may add and their limits."
          },
          "bots": {
            "type": "integer",
            "minimum": 0,
            "description": "Viewers the server keeps: they watch and step in as people would, through the same limits; counted in viewers."
          },
          "crowd": {
            "type": "boolean",
            "description": "The crowd's channel: a network with many bots stepping in, to see what a large audience does to it."
          },
          "you": {
            "type": "string",
            "description": "The asking address's name on air: two words and a number, the same for the same address."
          },
          "audience": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChannelViewer"
            },
            "description": "Who watches now: the addresses of the last 30 seconds by their names, then the bots."
          },
          "authors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Who took each impact of the plan, in its order: the names on air (empty for an impact from before the names)."
          },
          "supporters": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Who supported each impact of the plan, in its order: the names on air."
          },
          "supported": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "number",
                "minimum": 0
              }
            },
            "description": "When each support of each impact was given, in the order of `supporters`: world seconds of the season. A rewound broadcast counts only the support given by its moment."
          },
          "charges": {
            "$ref": "#/components/schemas/ChannelCharges"
          },
          "asked": {
            "type": "array",
            "items": {
              "type": "number",
              "minimum": 0
            },
            "description": "When each impact of the plan was asked for, in its order: world seconds of the season. A rewound broadcast shows only what was asked by its moment, the queue included."
          }
        }
      },
      "Channels": {
        "type": "object",
        "required": [
          "channels",
          "server_ms"
        ],
        "additionalProperties": false,
        "properties": {
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChannelSummary"
            }
          },
          "server_ms": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ChannelImpactRequest": {
        "type": "object",
        "required": [
          "kind"
        ],
        "additionalProperties": false,
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "partition",
              "offline",
              "latency",
              "selfish",
              "surge",
              "loss",
              "support"
            ]
          },
          "nodes": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0
            },
            "maxItems": 64,
            "description": "partition: one side (by default about half the mining power, each next split another way, so overlapping ones cut more parts); offline: one to six nodes; selfish and surge: one pool's node."
          },
          "value": {
            "type": "number",
            "minimum": 1,
            "maximum": 30000,
            "description": "latency: milliseconds on every link, 1000 to 30000, the largest going on applies; surge: the pool's power times 2 to 5 (3 by default), surges going on multiply up to ten times; loss: percent of messages lost, 1 to 50, the largest going on applies."
          },
          "id": {
            "type": "string",
            "description": "support: the id of another's impact going on or to come (plan.impacts); it lasts support_s longer, up to longest_s, once from a name."
          }
        },
        "description": "An impact, or a support of one (kind support with its id)."
      },
      "ChannelImpact": {
        "type": "object",
        "required": [
          "impact",
          "channel"
        ],
        "additionalProperties": false,
        "properties": {
          "impact": {
            "type": "object",
            "description": "The plan's new card: it starts the channel's lead (15 wall seconds by default) after the channel's time and lasts its length (90 wall seconds, a split 180, by default); impacts overlap and add up."
          },
          "channel": {
            "$ref": "#/components/schemas/Channel"
          }
        }
      },
      "ChannelNow": {
        "type": "object",
        "required": [
          "id",
          "name",
          "profile",
          "pace",
          "season",
          "started_ms",
          "ends_ms",
          "server_ms",
          "now_us",
          "version",
          "viewers",
          "bots",
          "crowd",
          "you",
          "audience",
          "charges"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "profile": {
            "type": "string",
            "description": "The network profile (GET /profiles)."
          },
          "pace": {
            "type": "integer",
            "minimum": 1,
            "description": "World seconds per wall second."
          },
          "season": {
            "type": "integer",
            "minimum": 0,
            "description": "The season's number: by default wall time over six hours since the Unix epoch; an admin's restart starts the next one at once. Each season the world starts anew."
          },
          "started_ms": {
            "type": "integer",
            "minimum": 0
          },
          "ends_ms": {
            "type": "integer",
            "minimum": 0
          },
          "server_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "The server's clock when it answered."
          },
          "now_us": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "The channel's world time: wall time since the season's start times the pace."
          },
          "version": {
            "type": "integer",
            "minimum": 0,
            "description": "Impacts in the season's plan: a new one means a new story."
          },
          "viewers": {
            "type": "integer",
            "minimum": 0,
            "description": "Addresses that asked for the channel in the last 30 seconds, and its bots."
          },
          "bots": {
            "type": "integer",
            "minimum": 0,
            "description": "Viewers the server keeps: they watch and step in as people would, through the same limits; counted in viewers."
          },
          "crowd": {
            "type": "boolean",
            "description": "The crowd's channel: a network with many bots stepping in, to see what a large audience does to it."
          },
          "you": {
            "type": "string",
            "description": "The asking address's name on air: two words and a number, the same for the same address."
          },
          "audience": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChannelViewer"
            },
            "description": "Who watches now: the addresses of the last 30 seconds by their names, then the bots."
          },
          "charges": {
            "$ref": "#/components/schemas/ChannelCharges"
          }
        }
      },
      "ChannelViewer": {
        "type": "object",
        "required": [
          "nick",
          "bot"
        ],
        "additionalProperties": false,
        "properties": {
          "nick": {
            "type": "string"
          },
          "bot": {
            "type": "boolean"
          }
        }
      },
      "ChannelSettings": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "pace",
          "lead_s",
          "length_s",
          "split_s",
          "gap_s",
          "per_hour",
          "going",
          "per_season",
          "bots",
          "bot_gap_s",
          "slots",
          "queue",
          "storm_s",
          "calm_s",
          "support_s",
          "longest_s",
          "charges",
          "charge_s",
          "costs"
        ],
        "properties": {
          "pace": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "description": "World seconds per wall second; a new pace starts a new season."
          },
          "lead_s": {
            "type": "integer",
            "minimum": 5,
            "maximum": 120,
            "description": "Wall seconds from an impact's request to its start."
          },
          "length_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 1800,
            "description": "Wall seconds an impact lasts."
          },
          "split_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 1800,
            "description": "Wall seconds a split lasts."
          },
          "gap_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3600,
            "description": "Wall seconds between an address's impacts (0 by default: the charges pace them)."
          },
          "per_hour": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3600,
            "description": "An address's impacts an hour."
          },
          "going": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200,
            "description": "The channel's impacts going on at once, of all kinds."
          },
          "per_season": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "The channel's impacts in a season."
          },
          "bots": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "bot_gap_s": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 10,
              "maximum": 7200
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "Wall seconds between a bot's impacts, drawn evenly from this range."
          },
          "slots": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 20
            },
            "minItems": 6,
            "maxItems": 6,
            "description": "Impacts of each kind going on at once, in the order partition, offline, latency, selfish, surge, loss; selfish and surge count per pool. An impact with no place waits in the queue."
          },
          "queue": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200,
            "description": "Impacts waiting for a place past their lead, at most."
          },
          "storm_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 3600,
            "description": "Wall seconds a storm lasts at most: an impact joins the impacts going on only while their storm with it stays this short, and a support grows an impact no further than its storm's bound (no less than length_s and split_s)."
          },
          "calm_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1800,
            "description": "Wall seconds of calm between storms: an impact joins the impacts going on, or starts this long after they are over (and before the next)."
          },
          "support_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 600,
            "description": "Wall seconds a viewer's support adds to an impact."
          },
          "longest_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 3600,
            "description": "Wall seconds an impact grows to at most by supports (no less than length_s and split_s)."
          },
          "charges": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "A viewer's charges on the channel: each impact and support costs some."
          },
          "charge_s": {
            "type": "integer",
            "minimum": 5,
            "maximum": 3600,
            "description": "Wall seconds for one charge to come back."
          },
          "costs": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 20
            },
            "minItems": 7,
            "maxItems": 7,
            "description": "Charges each costs, in the order partition, offline, latency, selfish, surge, loss, support; no more than charges."
          }
        }
      },
      "ChannelSettingsChange": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "pace": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "description": "World seconds per wall second; a new pace starts a new season."
          },
          "lead_s": {
            "type": "integer",
            "minimum": 5,
            "maximum": 120,
            "description": "Wall seconds from an impact's request to its start."
          },
          "length_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 1800,
            "description": "Wall seconds an impact lasts."
          },
          "split_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 1800,
            "description": "Wall seconds a split lasts."
          },
          "gap_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3600,
            "description": "Wall seconds between an address's impacts (0 by default: the charges pace them)."
          },
          "per_hour": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3600,
            "description": "An address's impacts an hour."
          },
          "going": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200,
            "description": "The channel's impacts going on at once, of all kinds."
          },
          "per_season": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "The channel's impacts in a season."
          },
          "bots": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "bot_gap_s": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 10,
              "maximum": 7200
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "Wall seconds between a bot's impacts, drawn evenly from this range."
          },
          "slots": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 20
            },
            "minItems": 6,
            "maxItems": 6,
            "description": "Impacts of each kind going on at once, in the order partition, offline, latency, selfish, surge, loss; selfish and surge count per pool. An impact with no place waits in the queue."
          },
          "queue": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200,
            "description": "Impacts waiting for a place past their lead, at most."
          },
          "storm_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 3600,
            "description": "Wall seconds a storm lasts at most: an impact joins the impacts going on only while their storm with it stays this short, and a support grows an impact no further than its storm's bound (no less than length_s and split_s)."
          },
          "calm_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1800,
            "description": "Wall seconds of calm between storms: an impact joins the impacts going on, or starts this long after they are over (and before the next)."
          },
          "support_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 600,
            "description": "Wall seconds a viewer's support adds to an impact."
          },
          "longest_s": {
            "type": "integer",
            "minimum": 10,
            "maximum": 3600,
            "description": "Wall seconds an impact grows to at most by supports (no less than length_s and split_s)."
          },
          "charges": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "A viewer's charges on the channel: each impact and support costs some."
          },
          "charge_s": {
            "type": "integer",
            "minimum": 5,
            "maximum": 3600,
            "description": "Wall seconds for one charge to come back."
          },
          "costs": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 20
            },
            "minItems": 7,
            "maxItems": 7,
            "description": "Charges each costs, in the order partition, offline, latency, selfish, surge, loss, support; no more than charges."
          }
        },
        "description": "Some of the settings: the rest stay as they are."
      },
      "AdminChannel": {
        "type": "object",
        "required": [
          "id",
          "name",
          "profile",
          "pace",
          "season",
          "started_ms",
          "ends_ms",
          "server_ms",
          "now_us",
          "version",
          "viewers",
          "bots",
          "crowd",
          "settings",
          "defaults",
          "people",
          "network",
          "impacts",
          "own_network"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "profile": {
            "type": "string",
            "description": "The network profile (GET /profiles)."
          },
          "pace": {
            "type": "integer",
            "minimum": 1,
            "description": "World seconds per wall second."
          },
          "season": {
            "type": "integer",
            "minimum": 0,
            "description": "The season's number: by default wall time over six hours since the Unix epoch; an admin's restart starts the next one at once. Each season the world starts anew."
          },
          "started_ms": {
            "type": "integer",
            "minimum": 0
          },
          "ends_ms": {
            "type": "integer",
            "minimum": 0
          },
          "server_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "The server's clock when it answered."
          },
          "now_us": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "The channel's world time: wall time since the season's start times the pace."
          },
          "version": {
            "type": "integer",
            "minimum": 0,
            "description": "Impacts in the season's plan: a new one means a new story."
          },
          "viewers": {
            "type": "integer",
            "minimum": 0,
            "description": "Addresses that asked for the channel in the last 30 seconds, and its bots."
          },
          "bots": {
            "type": "integer",
            "minimum": 0,
            "description": "Viewers the server keeps: they watch and step in as people would, through the same limits; counted in viewers."
          },
          "crowd": {
            "type": "boolean",
            "description": "The crowd's channel: a network with many bots stepping in, to see what a large audience does to it."
          },
          "settings": {
            "$ref": "#/components/schemas/ChannelSettings"
          },
          "defaults": {
            "$ref": "#/components/schemas/ChannelSettings"
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "nick",
                "seen_ms"
              ],
              "additionalProperties": false,
              "properties": {
                "nick": {
                  "type": "string"
                },
                "seen_ms": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "description": "The addresses that watch, by their names, and how long ago each asked."
          },
          "network": {
            "type": "object",
            "required": [
              "going",
              "parts",
              "off",
              "latency_ms"
            ],
            "additionalProperties": false,
            "properties": {
              "going": {
                "type": "integer",
                "minimum": 0
              },
              "parts": {
                "type": "integer",
                "minimum": 1
              },
              "off": {
                "type": "integer",
                "minimum": 0
              },
              "latency_ms": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "description": "The network now: impacts going on, its parts, nodes off and the largest delay."
          },
          "impacts": {
            "type": "object",
            "required": [
              "total",
              "ahead",
              "kinds",
              "authors"
            ],
            "additionalProperties": false,
            "properties": {
              "total": {
                "type": "integer",
                "minimum": 0
              },
              "ahead": {
                "type": "integer",
                "minimum": 0
              },
              "kinds": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "authors": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "nick",
                    "count"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "nick": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer",
                      "minimum": 1
                    }
                  }
                }
              }
            },
            "description": "The season's impacts: all, those yet to start, by kind, and who took the most."
          },
          "own_network": {
            "type": "boolean",
            "description": "Whether the channel runs on a network of its own (POST /admin/channels/{id}/network) rather than its profile's."
          }
        }
      },
      "AdminChannels": {
        "type": "object",
        "required": [
          "channels",
          "server_ms"
        ],
        "additionalProperties": false,
        "properties": {
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdminChannel"
            }
          },
          "server_ms": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "AdminChannelSet": {
        "type": "object",
        "required": [
          "id",
          "settings",
          "own_network",
          "season",
          "started_ms"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string"
          },
          "settings": {
            "$ref": "#/components/schemas/ChannelSettings"
          },
          "own_network": {
            "type": "boolean",
            "description": "Whether the channel runs on a network of its own (POST /admin/channels/{id}/network) rather than its profile's."
          },
          "season": {
            "type": "integer",
            "minimum": 0
          },
          "started_ms": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ChannelCharges": {
        "type": "object",
        "required": [
          "have",
          "most",
          "next_s"
        ],
        "additionalProperties": false,
        "description": "The asking address's charges on the channel.",
        "properties": {
          "have": {
            "type": "number",
            "minimum": 0,
            "description": "Charges now, a part of one coming back counted (two decimals)."
          },
          "most": {
            "type": "integer",
            "minimum": 1
          },
          "next_s": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "description": "Wall seconds until the next whole charge; null when full."
          }
        }
      }
    }
  }
}
