Viewer and cheme serve¶
cheme serve serves a plant repository read-only as a JSON API on 127.0.0.1:8765, and with
--ui a built viewer at /. The generated
serve API reference lists every route with its parameters; this
page describes what they return and the rules the server keeps.
cheme serve [--repo DIR] [--library] [--host 127.0.0.1] [--port 8765] [--ui DIST_DIR] serves the plant
repository as JSON over HTTP, for the viewer. Every GET only reads. It needs the serve extra
(uv tool install 'cheme-as-code[serve] @ git+...'). Every endpoint takes an optional rev (a
branch, tag or sha; absent: the working tree) and repo-relative POSIX paths:
| endpoint | returns |
|---|---|
GET /api/repo |
name, root_name, branch, head, stage, stages, sheets (each with kind pid, bfd or pfd), dirty, has_layout, has_lca, has_tea (estimate/tea.yaml), has_equipment_estimate (the equipment list lists items), has_simulation (process/simulation.yaml), has_reactions (process/reactions.yaml), has_kinetic_data (experiments/kinetics/*.yaml) |
GET /api/streams?case=&rev= |
the stream table of process/flowsheet.yaml: streams (values with unit, provenance, source and stale flag, composition, calculated values, links with resolved/problem, imbalance, on_bfd), balances, nodes, bfd_connections (BFD connection id to the streams it represents, read from the built BFD sheet's represents: descriptions, else from the flowsheet; bfd_connections_from says which: sheet or flowsheet), cases, units, tolerance; 404 without a flowsheet, 422 for one that does not load or an unknown case |
GET /api/simulation?case=&rev= |
{rev, file, run_file, case, cases, spec, run, stale, staleness, units, problem, findings}: spec is process/simulation.yaml parsed (simulator, property_package, components, tolerance, tear_streams, exclude, guesses, specs per step as written, with provenance); run the case's entry of process/simulation-run.json (null without one); stale whether that run was simulated from other inputs (model fingerprint; null without a run or when the flowsheet does not load), staleness {model_fingerprint, current_fingerprint, changed_files}; units one row per step {id, unit, status (simulated, excluded, refused, not-simulated), reason, spec, run}; findings the translation's; 404 without process/simulation.yaml, 422 when it does not load or for an unknown case |
GET /api/reactions?case=&rev= |
{rev, file, case, cases, reactions, reactors, balance, kinetics}: each reaction as declared (stoichiometry, equation, phase, reversible, conversion, heat_of_reaction, equilibrium_constant with provenance, catalyst, sources) plus key_component, reaction_smiles (the reaction's external identifier, reactants>>products from the stoichiometry and the components' SMILES; null unless every component has one and every coefficient is whole), type (the reactor model it runs in: conversion, equilibrium, gibbs, mixed or null), units ({unit, kind, model, approach_K, conversion} per block or step naming it) and element_balance; reactors the cheme process reactors --json rows; balance {balances, findings} (the reactive balances and the reaction, reactor and balance findings of validate); per reaction its rate as declared (form, basis, driving, k {A, Ea, n, T_ref}, orders, adsorption, catalyst, temperature_range, provenance, source) and rate_law (null without a rate: {expression, k_expression, rate_unit, driving_unit, k_unit, catalyst, k_profile: [{temperature_K, k}], k_profile_from}, k(T) over the rate's temperature range, else its catalyst's window); kinetics {file, catalysts, findings}: each catalyst of process/catalysts.yaml as declared plus reactions (ids it catalyses) and units ({unit, kind, reactor, reactions, bed} per block or step holding it), the cheme.catalyst-* and cheme.process-catalyst findings (file null and catalysts empty without the file); a solved fixed bed is the bed of its reactors row, and each reactors row says its gas model (fugacity: ideal or peng_robinson); a reaction with a mechanism has microkinetics (null without one): {conditions: {unit, stream, temperature_K, pressures_bar}, missing, ...}, the cheme chemistry microkinetics --json analysis (coverages, TOF, degree of rate control, apparent Ea, orders) at the outlet of its first reactor, or missing naming why there is none; 404 without process/reactions.yaml or a flowsheet, 422 when either does not load or for an unknown case |
GET /api/kinetics?rev= |
{rev, directory, files: [{file, data, fit, problem}]}: each kinetic data file of experiments/kinetics/ as declared and its default fit (cheme experiments fit-rate --json: a power law in the reaction's reactants, over 1 − Q/K when the reaction is reversible and K is known; fit.reverse says which), or the problem with either (where 1 − Q/K fails at a point, a forward-only fit with a note); a flowsheet that does not load is not needed; cheme export-static --exclude leaves out excluded files; 404 without data files or process files |
GET /api/library?rev= |
{rev, entries}: the reaction library (cheme chemistry library), every entry (window, components, reactions, catalysts, model basis, sources), each item's in_plant saying what the plant has (a component: the plant's id with its CAS number, else null; a reaction: the same stoichiometry under its id; a catalyst: its id, with differs when the plant's has other data, so --add refuses it); without process files nothing is marked, and a flowsheet that does not load leaves the marks from the materials, reactions and catalysts |
GET /api/branches |
[{name, head, current}] |
GET /api/log?limit=50&path=&rev= |
[{sha, short, author, date, subject}] |
GET /api/tree?rev= |
[{path, type, kind}], kind pid, yaml, markdown, json or other |
GET /api/file?path=&rev= |
the file as text (400 for binary or over 5 MB) |
GET /api/pid.svg?path=&rev= |
the sheet rendered, as cheme render draws it |
GET /api/diff?path=&old=HEAD&new= |
changes, both SVGs, highlight maps, old_owner/new_owner (changed id to drawn id), status (added/removed for a sheet on one side), review-text diff |
GET /api/interlocks?rev= |
{rev, file, interlocks, columns (effect tags), rows: [{id, title, cause, condition, sheet, sil, cells}]}: the cause-and-effect list and matrix; 404 without control/cause-effect.yaml |
GET /api/control |
the cheme control report --json report of the working tree: loops, sequences, the narrative and the control philosophy with their review status and findings; see docs/framework/control.md; 404 without control records, 422 when they do not load |
GET /api/issues |
the cheme issues --json report: the plant's GitHub issues as engineering items (type, gate, discipline, source, status), open_by_gate, blocking per stage and new links to the issue forms; see docs/framework/issues.md; cached two minutes; 404 when plant.yaml declares no issues, 503 when they cannot be read (no token, no network) |
GET /api/status |
the cheme status --json summary of the working tree, without evaluating the gates |
GET /api/impact |
the cheme impact --json report of the working tree: {commit, findings}, every finding cheme.review-needed at level info, with its id |
GET /api/validate |
the cheme validate --json report of the working tree |
GET /api/maturity?through= |
the cheme maturity --json report of the working tree, evaluated on every request (one at a time) and never written; without through the engineering level runs the whole validation (some 30 s on ASU-1), through=predictive or lower skips it; see docs/framework/maturity.md; 400 for an unknown level |
GET /api/checks?rev= |
checks/status.json ({checks: []} when absent) |
GET /api/session |
{token, header}: the per-launch token for POST /api/checks/run |
POST /api/checks/run |
the gates evaluated now on the working tree (not written); needs the token header |
GET /api/yaml?path=&rev= |
{data, kind, schema} for a .yaml/.yml mapping or list; kind is a list or layout kind, plant, materials, flowsheet or null |
GET /api/layout?rev= |
{site, areas, roads, racks, buildings (with footprint), equipment: [{tag, type, service, position, shape, family, size [dx, dy, dz], size_from (list, placement, default), rotation_deg, on, building, level, sheet, sheet_file, bbox, clash, notes}], lines: [{number, from, to, from_point, to_point, distance, length_est, racks, finding}], separations, model, notes, files, rev, findings} with the layout findings; separations as written in the site plan, model the generated layout/plant-model.yaml (null without one; see docs/framework/layout-model.md); 404 without layout files |
GET /api/layout/plan.svg?rev=&level=&z= |
the plot plan of the committed layout/plant-model.yaml, or a level plan (level= a structure or building level id, or z=a,b an elevation range); see docs/framework/layout-model.md; 404 without a model or for an unknown level, 400 for a bad z |
GET /api/layout/section.svg?rev=&x=&y=&depth=6 |
a section of the committed model at x= or y= (one of them), depth m thick (0 to 1000); 404 without a model, 400 for bad arguments |
GET /api/mto?rev= |
the material take-off of the committed layout/plant-model.yaml: {rev, model, model_sha256, columns, rows, totals, findings, estimate}, estimate priced with estimate/rates.yaml (null without one); see docs/framework/mto.md; 404 without a model |
GET /api/estimate/equipment?rev= |
the equipment estimate (cheme assess equipment --json): {rev, currency, base, index, estimate_class, accuracy, totals, lines, own_costs, findings, source}; see docs/framework/mto.md; 404 without an equipment list or with no items, 422 for a list that does not load |
GET /api/datasheets?rev= |
the equipment datasheets (equipment/datasheets/): {rev, directory, datasheets: [{tag, file, valid, type, service, draft, counts, drawing, drawing_current, findings, ...}], missing, source}; see docs/framework/datasheet-drawings.md; 404 without datasheets |
GET /api/datasheets/{tag}?rev= |
one datasheet: {rev, tag, file, summary, datasheet (parsed, null when it does not validate), data, drawing: {file, state, current}, findings, source}; 404 without it |
GET /api/datasheets/{tag}.svg?rev=&generated= |
its outline drawing: the committed SVG, or the generated one when none is committed or with generated=true; headers X-Cheme-Drawing (committed or generated) and X-Cheme-Drawing-Current; 422 for a datasheet that does not validate |
GET /api/lca?rev= |
the LCA (cheme assess lca --json without the source): inventory, impacts by scenario, category, phase and contributor, findings; see docs/framework/lca.md; 404 without lca/lca.yaml or plant.yaml, 422 when it does not compute |
GET /api/tea?rev= |
the techno-economic analysis (cheme assess tea --json without the source): premises, capex build-up, flows, cash flow, results, scenarios, tornado, break-even values, Monte Carlo, findings; see docs/framework/tea.md; 404 without estimate/tea.yaml, 422 when it does not compute |
Errors are {"error": "..."} with 400 (bad path or revision, file over 5 MB), 403 (cross-site
request, missing token), 404 (absent), 405, 409 (checks already running), 422 (cannot load) or
500 (internal error, no details). POST /api/checks/run runs the kind: command checks of
checks/checks.yaml, which are shell commands: one run at a time, each command stopped after
120 s. /api/* refuses requests another web site makes (Sec-Fetch-Site cross-site or
same-site, or a foreign Origin). YAML is returned with aliases expanded up to 200,000 values;
self-referencing aliases and !!binary are 422. Sheets that plant.yaml lists outside the
repository are never read; validate reports them as cheme.sheet-outside-repo.
Paths must stay inside the repository (no .., absolute paths, .git, or symlinks out);
revisions are resolved with git rev-parse --verify --end-of-options and may not start with -.
The server binds 127.0.0.1 and accepts only local Host headers. With --ui (or CHEME_UI)
it also serves a built single-page app at /, falling back to its index.html. Rendered sheets
and diffs are cached in memory by git blob id.
A folder of plants (library mode, ADR 0034). cheme serve in a folder that has no
plant.yaml above it but plants below it (at most 3 levels down: process-library's root, or a
folder of plant repositories), or with --library, serves every plant under /p/<id>/ (the id
is the plant's folder name; when two share it, their relative paths, a-same). Each plant's
endpoints are the ones above, unchanged (/p/asu-1/api/repo); a plant in a subfolder of a git
repository reads its history from that repository, a plant repository its own. The library adds:
| endpoint | returns |
|---|---|
GET /api/plants |
{root_name, plants: [{id, path, name, stage, sheets, maturity, maturity_checked, title, summary, families, error}]}: name, stage and the number of sheets from plant.yaml (error when it does not validate), the recorded maturity level of checks/maturity.json (never evaluated live), and the optional title, summary and families of a process.yaml beside it |
/p/<id> redirects to /p/<id>/; an unknown id is a 404. With --ui the viewer is served at
/ (its index of the plants) and at every /p/<id>/ page. The same guards apply to the library
and to each plant (the Host check, the cross-site refusal, a session token per plant).
Serving to the network¶
By default the server binds 127.0.0.1 and answers only requests whose Host header names
127.0.0.1, localhost or ::1. Other names are refused with 400. This blocks DNS
rebinding: a web page cannot point its own host name at the server and then read the
repository. The rule still applies with --host 0.0.0.0 (or ::), because a wildcard address
names no host. Pass each name or address other machines use to reach the server with
--allow-host (repeatable; wildcards are refused). A specific --host address is accepted
as well:
cheme serve --host 0.0.0.0 --allow-host 192.168.1.5 --allow-host box.lan
Whoever reaches the port by one of those names can read every file of the repository. On a
non-local address the server also refuses GET /api/session and POST /api/checks/run
(403), so nobody on the network can run the command checks of checks/checks.yaml.
--allow-remote-checks turns them back on.
The viewer's pages carry Content-Security-Policy: frame-ancestors 'self' and
X-Frame-Options: SAMEORIGIN: only the served origin may frame them, so another site cannot
trick a click on "Run now" inside a frame.