SBGN Layout/API Back to the app

HTTP API

The same engine that powers the page is available over HTTP, so you can lay out maps from scripts, notebooks or a CI job. Two styles are offered: a synchronous endpoint that returns the finished files in one request, and an asynchronous job API with live progress. All responses are UTF-8; errors are JSON objects with an error field.

Quick start

Lay out a map and receive a ZIP with the laid-out SBGN-ML, the SVG drawing and a JSON report:

curl -sS -F "[email protected]" https://sbgn-layout.org/api/layout -o glycolysis_layout.zip

Ask for JSON instead (inline SVG and SBGN-ML strings plus the report):

curl -sS -H "Accept: application/json" -F "[email protected]" https://sbgn-layout.org/api/layout | jq '.report.timings'

Through the public URL a request is cut off after about 100 seconds (HTTP 524), queue time included. For large maps use the asynchronous jobs below, which have no such limit.

Options are form fields or query parameters, e.g. stop a process-description map before edge routing and skip the process stars:

curl -sS -F "[email protected]" "https://sbgn-layout.org/api/layout?steps=5&stars=0" -o map_layout.zip

Asynchronous jobs

For large maps, or when you want progress, create a job and follow it. Results are kept on the server for about two hours. A job belongs to whoever created it: the first request receives a cookie (sbgn_owner), and only requests carrying that cookie can see, download or delete the job. Everyone else gets 404, as for an unknown id. Keep the cookie in a cookie jar (-c / -b in curl):

# 1. create the job (HTTP 202); the cookie jar keeps your owner cookie
JOB=$(curl -sS -c jar.txt -b jar.txt -F "[email protected]" -F "perStep=1" -F "original=1" https://sbgn-layout.org/api/jobs | jq -r .id)

# 2. follow progress as Server-Sent Events (one JSON object per "data:" line)
curl -sS -N -b jar.txt https://sbgn-layout.org/api/jobs/$JOB/events

# 3. fetch the result and the files
curl -sS -b jar.txt https://sbgn-layout.org/api/jobs/$JOB | jq '.report.conflicts'
curl -sS -b jar.txt -o result.svg  https://sbgn-layout.org/api/jobs/$JOB/files/map_layout.svg
curl -sS -b jar.txt -o result.sbgn https://sbgn-layout.org/api/jobs/$JOB/files/map_layout.sbgn
curl -sS -b jar.txt -o all.zip     https://sbgn-layout.org/api/jobs/$JOB/files/all.zip

Event types on the stream: status (queued · running · done · error, with queuePosition), language, graph (node/edge counts), step (index, name, running/done, ms, constraints added), log, done, error. The stream replays the history first, so connecting late is fine.

Open one of your finished jobs with /?job=<id>; the link works only in the browser that started the job.

With perStep=1 every step writes a snapshot. The snapshot for step 1 of a process-description map is the skeleton graph the step builds, not the input again; its file is also listed as outputs.skeletonSvg. It shows the skeleton after the unconstrained force-directed pass that opens step 2 (with skeletonShape=fd the angles of its edges decide the alignments), so the file is written after step 2 but keeps step index 1. With steps=1 or skeletonShape=topology that pass does not run and the snapshot shows the input positions; graphLayout in the report's first step says which (fd or input).

Endpoints

Method · pathPurpose
POST /api/layoutSynchronous. Multipart field file (+ options). Returns a ZIP, or JSON with Accept: application/json / ?preview=1. Response header X-Job-Id.
POST /api/jobsCreate a job (202). Same fields as above. Returns the job summary with id, status, queuePosition. HTTP 429 when you already have 5 jobs queued or running.
GET /api/jobs/{id}Summary while running; the full result when finished: svg, sbgn, originalSvg, stepSvgs, report, files, log. ?summary=1 for the summary only.
GET /api/jobs/{id}/eventsServer-Sent Events with progress.
GET /api/jobs/{id}/files/{name}Download one output file (see files in the result), all.zip for everything, or input for the uploaded map. ?inline=1 serves the file inline.
DELETE /api/jobs/{id}Remove a finished job and its files.
GET /api/jobsYour recent job summaries (those created with your cookie).
GET /api/examplesThe bundled sample maps (served from /examples/<name>).
GET /api/healthJava runtime, runner JAR, queue state. HTTP 503 when the engine cannot run.

Options

FieldValuesMeaning
languageauto · PD · ER · AFForce the SBGN language; auto reads it from the map (default).
steps1 … 99Run only the first N pipeline steps (PD has 6, ER 4, AF 7; a larger number runs all). The last step is edge routing, which for ER and AF also restores tags and compartment frames. Omit to run everything.
perStep0 · 1Write an SVG after every step (<name>_stepN.svg). Default 1 for jobs, 0 for /api/layout.
original0 · 1Write an SVG of the input before layout (<name>_original.svg). Same defaults as perStep.
png0 · 1Also render a PNG (longest side 4000 px).
stars0 · 1PD step 4 “Process Stars” on/off (default on).
starRadius40 … 400Radius of the process stars in px (default 100).
skeletonShapefd · structure · structure-map · topologyHow step 2 fixes the skeleton directions. fd uses a force-directed pass. structure uses local chains; its later geometry still depends on the starting layout. structure-map takes directions from the input drawing. Experimental topology uses a global directed plan, includes regulation, handles feedback explicitly and supplies canonical starting positions and process axes; its step-1 snapshot keeps the input positions.
compartmentsdrop · keepCompartment frames distort the layout, so they are removed by default. keep lays the map out per compartment and draws the frames again.
improvements0 · 1All layout improvements in one switch (default on, as in the VANTED GUI). They are made for PD maps; on ER and AF maps only the clipping of arcs at tags applies. With skeletonShape=fd the layout changes: parallel reactions become lenses or fans, rings and chains bend at species, a process takes its axis from the most opposed pair and gives the axis slot to the main metabolite, cofactor rows spread, a modulator keeps its side, logic inputs lie beyond the gate, arcs stop at tags, separate parts move to where they cross least, and the pass on the full map starts from the skeleton layout. In every skeletonShape the SBGN-ML export keeps the input port groups of each process, which can cost a crossing where an arc docks on the far side.

Output files and report

The pipeline

Process description maps run through six steps; every step adds constraints and re-solves the layout with the constrained force-directed solver (cola).

  1. Skeleton. Removes docking and compartments, reroutes tags, remembers complexes and builds the skeleton graph (processes and their structural neighbours).
  2. Skeleton Constraints. Runs an unconstrained force-directed pass on the skeleton (not with skeletonShape=topology), chooses one shortest metabolic cycle per biconnected block (Tarjan), embeds each as a rectangle, and aligns the remaining skeleton edges along their dominant axis.
  3. Individual Constraints. Determines the substrate/product axis of every process and adds the separation constraints for its left- and right-hand side.
  4. Process Stars. Places the leaves of a process on a symmetric star: main substrate and product on the axis, cofactors on mirrored diagonals, the enzyme on the free perpendicular side.
  5. Stubs. Adds stub constraints for catalyses and stimulations on a free side of the process, then runs the hierarchical compartment layout.
  6. Edge Routing. Restores complexes and tags, routes the edges orthogonally with Adaptagrams (libavoid) and re-inserts the compartments around their members.

Entity relationship maps run through four steps. The arc is the unit there: an arc keeps its outcomes and ports on one straight line, and every arc attached to them leaves at a right angle.

  1. Force-Directed. Prepares the graph (docking, compartments, tags) and takes the hyperedge nodes (outcomes, ports) out, so every arc between two glyphs becomes one edge; one force-directed pass over the glyphs.
  2. Skeleton Constraints. Gives every arc between two glyphs the axis of its force-directed angle: both glyphs on one line, in order, with room for the arc's nodes.
  3. Hyperedge Nodes. Puts the outcomes and ports back in line with their arc's ends; every other arc there (influences, logic arcs) leaves perpendicular.
  4. Edge Routing. Restores tags, routes the edges with Adaptagrams, re-inserts compartments and ends arcs on pins at the glyph borders.

Activity flow maps run through seven steps that build a hierarchy instead of process templates.

  1. LO Trees. Prepares the graph; every tree of logical operators, with the inputs that feed only it, is laid out on its own and stands in as one placeholder box.
  2. Feedback Arc Set. Removes a minimal, stable feedback arc set so the influences form a DAG. Nothing moves in this step.
  3. Skeleton. Aligns a longest path of the DAG top to bottom in one column per component.
  4. Levels. Adds the nodes joined to the previous level, sources above and targets below their anchor.
  5. Insert LO Trees. Replaces the placeholders by their trees, turned towards their output, and takes the feedback arcs back in.
  6. LO Stubs. Puts an operator's inputs on its input side and its output on the other; final constrained force-directed run.
  7. Edge Routing. Restores tags, routes the edges with Adaptagrams and re-inserts compartments.

Constraints that contradict each other (e.g. separations and alignments closing a cycle) are detected by a feasibility guard before the solver runs; automatically generated ones are dropped and listed as conflicts in the report, so a dropped constraint never silently distorts the result.

Python example

import requests, time

s = requests.Session()  # keeps the owner cookie, so the job stays visible to you
with open("map.sbgn", "rb") as f:
    job = s.post("https://sbgn-layout.org/api/jobs", files={"file": f}, data={"perStep": "1"}).json()

while True:
    r = s.get(f"https://sbgn-layout.org/api/jobs/{job['id']}").json()
    if r["status"] in ("done", "error"):
        break
    time.sleep(1)

if r["status"] == "done":
    open("map_layout.svg", "w").write(r["svg"])
    open("map_layout.sbgn", "w").write(r["sbgn"])
    print("conflicts:", len(r["report"]["conflicts"]), "layout ms:", r["report"]["timings"]["layoutMs"])
else:
    print("failed:", r["error"])

Limits and configuration