Web Viewer Server API#
This document describes the wire protocol between the OpenROAD web viewer’s browser frontend and its C++ server. The protocol is intended to be stable enough that an alternate client (custom browser app, headless test driver, third-party visualization) can be implemented against it without reading the JavaScript reference frontend.
The server exposes one HTTP route for static assets and one WebSocket endpoint for all dynamic operations. The WebSocket carries a small binary framing with JSON or PNG payloads.
Connecting#
HTTP and WebSocket share one TCP port (default
8080, configured viaweb_server -port). A single Boost.Beast listener serves both.HTTP
GET /returnsindex.html;GET /<path>returns the embedded asset at that path (*.js,*.css, etc.). 404 otherwise. No method other than GET is supported.The WebSocket upgrade is also at the root path. Once upgraded, all request/response traffic uses the binary framing below.
Wire frame format#
Every WebSocket message — both client→server and server→client — is a binary frame with the following layout:
Bytes |
Field |
Type |
Description |
|---|---|---|---|
0…3 |
|
|
Request correlator (see Correlation). |
4 |
|
|
Payload type: |
5…7 |
reserved |
|
Must be zero. |
8… |
|
bytes |
UTF-8 JSON, PNG-encoded image, or UTF-8 error string. |
Correlation#
The client picks a 32-bit
idfor each request and the server echoes it on the corresponding response. The reference frontend uses a monotonically-increasing counter; any unique value is fine.Server-push messages (broadcasts that aren’t replies to a specific request) use
id = 0.
Payload types#
0 = JSON— body is a UTF-8 JSON document. Default response shape.1 = PNG— body is a raw PNG file. Used bytileandheatmap_tile.2 = Error— body is a UTF-8 plain-text error message. Surfaces in the server log asWEB-0043(see Error contract below).3 = Empty— the tile is blank: the renderer drew nothing in it. The body is zero-length. Returned bytile,overlay_tileandheatmap_tilein place of a fully transparent PNG, which would still cost the client a decode and a full-size bitmap. Most tiles in a viewport are this: every layer with no geometry where the user is looking. A client must treat it as “draw nothing here” and, on a refresh, must release any image the tile was previously holding.
Request envelope#
Every request from the client is a JSON object with at least:
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Request id; echoed back unmodified. |
|
|
✓ |
Selects the handler. See per-type sections. |
The rest of the object’s fields are handler-specific.
A request with malformed JSON, a non-object root, or missing/wrongly-typed
id or type produces a kError response of the form
Malformed request (missing or invalid id/type) and a WEB-0043
warning. A well-formed request whose type is not registered produces
Unknown request type: <type> and the same warning.
Wire conventions#
The server is strict on field types. Every field below has one
canonical JSON type; sending any other type for a present field yields a
kError response.
Integers travel as JSON numbers parsed as
int64.Booleans travel as JSON
true/false.Doubles are JSON numbers; whole-valued numbers may parse as integers on the wire, which the server tolerates for double-typed fields only.
Strings are JSON strings; arrays are JSON arrays.
Two narrow exceptions documented inline:
select.zoomacceptsintordouble(Leaflet’szoomSnap: 0permits fractional zoom values).set_heatmap.valueforint-typed settings acceptsintordoubleand rounds (the JS frontend always usesparseFloat).
A “✓” in the Required column means the server reads the field
unconditionally. Missing fields throw and produce a kError.
Server-push messages#
These arrive with id = 0 and type = JSON. The client subscribes by
simply listening on the WebSocket; there’s no opt-in.
|
Trigger |
Body |
|---|---|---|
|
Initial spatial-index build finished after a connection / |
|
|
Buffered server log lines being flushed to clients. |
|
|
The graphics-debug pause hook entered (e.g. placer breakpoint). |
|
|
Pause hook exited. |
|
|
Debug overlay needs a re-render. |
|
|
Server is exiting; client should not auto-reconnect. |
|
Tile rendering#
Tile sizing#
tile, overlay_tile and heatmap_tile all accept the same two optional
sizing fields, and a client that draws them on top of one another must send
the same values to all three — a highlight rendered at a different pixel
count is rescaled by the browser and no longer sits on the shape it
highlights.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
The display’s device pixel ratio. Clamped to |
|
|
— |
The exact device-pixel side the client will display the tile in. Clamped to |
Sent explicitly rather than derived from dpr, because a tile’s CSS box is
only a whole number of device pixels when tileSize × dpr is an integer.
tile#
Render a single 256×256 PNG tile of the layout.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Layer name ( |
|
|
✓ |
Leaflet tile zoom level (0 = whole design). |
|
|
✓ |
Tile column at zoom |
|
|
✓ |
Tile row at zoom |
|
|
✓ |
Tech layers currently visible on the pins synthetic layer; empty array means hide all pin markers. |
visibility flags |
|
per-flag default |
See TileVisibility flags below. Any flag may be omitted to take the default. |
|
|
— |
Per-row-site visibility. Only consulted when |
Response: PNG image (frame type 1), or frame type 3 with no body
when the layer has nothing to draw in this tile.
overlay_tile#
Render one tile of the highlight overlay — selection, hover, timing paths,
DRC markers, route guides, flight lines and user labels — on a transparent
background, on the same tile grid as tile. Kept separate from the layer
tiles so a selection change repaints one pane instead of re-rendering every
layer’s geometry.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Leaflet tile zoom level, as for |
|
|
✓ |
Tile column at zoom |
|
|
✓ |
Tile row at zoom |
|
|
— |
Tech layers currently visible. Consulted so route guides respect layer visibility; omitted means draw guides on every layer. |
|
|
— |
Default |
|
|
— |
Default |
|
|
— |
Default |
|
|
— |
Default |
|
|
— |
Default |
Response: PNG image (frame type 1), or frame type 3 with no body
when nothing is highlighted in this tile — which is the usual case, since
the overlay holds nothing at all until something is selected.
bounds#
Return the design’s bounding box and shape-cache readiness.
No request fields beyond the envelope.
Response (JSON):
{
"bounds": [[yMin, xMin], [yMax, xMax]],
"shapes_ready": true,
"pin_max_size": 200
}
bounds is in DBU. shapes_ready indicates whether the spatial index
finished building (the refresh push fires when this flips to true).
pin_max_size is the largest BPin dimension in DBU, used by the client
to size the pin-marker overlay.
tech#
Return tech-layer metadata, sites, and block info.
No request fields.
Response (JSON):
{
"layers": ["metal1", "via1", "metal2", "..."],
"layer_colors": [[r, g, b], ...],
"sites": ["FreePDK45_38x28_10R_NP_162NW_34O", "..."],
"has_liberty": true,
"dbu_per_micron": 1000,
"block_name": "top"
}
layer_colors is parallel to layers; each entry is RGB in 0..255.
block_name is the empty string when no block is loaded.
Selection and inspection#
select#
Pick the topmost selectable object at a DBU coordinate. Cycles through overlapping objects on repeated calls at the same point.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
X coordinate in DBU. |
|
|
✓ |
Y coordinate in DBU. |
|
|
✓ |
Current Leaflet zoom level. Server truncates fractional values. |
|
|
✓ |
Same semantics as |
visibility flags |
|
per-flag default |
Same as |
|
|
— |
Same as |
Response (JSON): the inspect payload (see inspect below) plus a
top-level selected array listing every overlapping candidate:
{
"selected": [
{"name": "buf1", "type": "Inst", "bbox": [xMin, yMin, xMax, yMax]},
...
],
"can_navigate_back": 0,
"name": "buf1",
"type": "Inst",
"properties": [...],
"bbox": [xMin, yMin, xMax, yMax]
}
When no object is selectable at the point, selected is [] and only
can_navigate_back is set (no name/type/properties).
inspect#
Re-inspect a previously-selected object using its session-local id from
a prior select.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
ID returned in a previous |
Response (JSON): same shape as the inspect portion of select’s
response, without the selected array:
{
"can_navigate_back": 1,
"name": "buf1/Z",
"type": "ITerm",
"properties": [
{"name": "Master", "value": "BUF_X16",
"value_select_id": 12},
{"name": "Children", "children": [
{"name": "child", "value": "...", "value_select_id": 13},
...
]}
],
"bbox": [xMin, yMin, xMax, yMax],
"has_guides": 1
}
*_select_id fields appear next to any property whose value is itself
selectable; the client passes them back via inspect to drill down.
has_guides is present (and 1) only when the inspected object is a
net with route guides. can_navigate_back is 1 whenever the
back-navigation history is non-empty.
If select_id is out of range, the response includes
{"error": "invalid select_id"} instead of the metadata.
inspect_back#
Pop the back-navigation stack and re-emit the inspect payload for the previous object. No request fields.
Response (JSON): same shape as inspect but for the previous object.
hover#
Highlight an object’s shapes (mouseover preview) without selecting it.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Selectable id; pass |
Response (JSON):
{"ok": 1, "count": 3, "rects": [[xMin, yMin, xMax, yMax], ...]}
count is the number of highlight rects collected; an empty rect array
falls back to the object bbox.
snap#
Find the nearest design edge to a cursor position (for the ruler / crosshair tool).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Cursor X in DBU. |
|
|
✓ |
Cursor Y in DBU. |
|
|
✓ |
Search radius in DBU. |
|
|
✓ |
If two snap candidates fall within this many DBU of each other, prefer the point endpoint. |
|
|
✓ |
Allow snapping to horizontal edges. |
|
|
✓ |
Allow snapping to vertical edges. |
|
|
✓ |
Layers to consider for routing snaps. |
visibility flags |
|
per-flag default |
Same as |
Response (JSON):
{
"found": true,
"is_point": false,
"edge": [[x1, y1], [x2, y2]]
}
is_point is true when the snap collapsed to a point (e.g. corner).
On "found": false, edge and is_point are absent.
Schematic#
schematic_cone#
Build a cone of influence around an instance (fanin/fanout) and return it as a Yosys-format netlist for the netlistsvg renderer.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Anchor instance name. |
|
|
✓ |
BFS depth upstream ( |
|
|
✓ |
BFS depth downstream. |
Response (JSON): a Yosys netlist JSON
(schema)
limited to one module named top:
{
"modules": {
"top": {
"attributes": {},
"ports": {"<bterm name>": {"direction": "input", "bits": [n]}},
"cells": {"<inst name>": {"hide_name": 0, "type": "<master>", "attributes": {}, "parameters": {}, "port_directions": {...}, "connections": {...}}},
"netnames": {"<net name>": {"hide_name": 0, "bits": [n], "attributes": {}}}
}
}
}
Cones are capped at 150 instances; nets with fanout > 30 are skipped to keep the schematic readable.
schematic_full#
Same shape as schematic_cone but emits the entire block (no caps). No
request fields.
schematic_inspect#
Inspect a single instance by name (used when the schematic widget
selects a cell). Equivalent to a select+inspect pair but skips the
geometric pick.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Instance name to inspect. |
Response (JSON): same shape as inspect.
Tcl#
tcl_eval#
Evaluate a Tcl command. Captures info log output and the command’s
result.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
The Tcl source string. |
Response (JSON):
{
"output": "captured stdout/log lines",
"result": "Tcl_GetStringResult(interp)",
"is_error": false
}
When the user runs exit or quit from the browser, the server’s
override turns the result into a shutdown signal:
{
"output": "...",
"result": "Exiting OpenROAD.",
"is_error": false,
"action": "shutdown"
}
Clients that see action: "shutdown" should disable auto-reconnect.
tcl_complete#
Tab-completion for the Tcl prompt. Splits the line at the cursor and reports candidate completions plus the replacement range.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Full input line. |
|
|
✓ |
Cursor offset into |
Response (JSON):
{
"completions": ["...", "..."],
"mode": "commands" | "variables" | "arguments",
"prefix": "the matched prefix",
"replace_start": 4,
"replace_end": 8
}
Timing and charts#
timing_report#
Return the worst N timing paths.
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
✓ |
— |
|
|
|
✓ |
— |
Maximum number of paths to return. |
|
|
|
Lower slack bound (inclusive). Optional filter. |
|
|
|
|
Upper slack bound (exclusive). Optional filter. |
Response (JSON):
{
"paths": [
{
"start_clk": "clk", "end_clk": "clk",
"required": 1.0, "arrival": 1.5,
"slack": -0.5, "skew": 0.0,
"path_delay": 0.9, "logic_depth": 4, "fanout": 12,
"start_pin": "ff1/CK", "end_pin": "ff2/D",
"data_nodes": [{"pin": "...", "fanout": 1, "rise": true, "clk": false, "time": 0.0, "delay": 0.0, "slew": 0.0, "load": 0.0}, ...],
"capture_nodes": [{...}]
},
...
]
}
timing_highlight#
Highlight one timing path’s shapes on the layout, optionally with a single stage emphasized.
Field |
Type |
Required when |
Description |
|---|---|---|---|
|
|
always |
Index into the most recent |
|
|
|
Which side of the report |
|
|
optional, |
If set, emphasize this pin’s net within the path. |
Response (JSON): {"ok": true}. The actual update is the layer
overlay redraw on the next tile request.
slack_histogram#
Histogram of endpoint slacks for one side of the timing report.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Setup vs hold endpoints. |
|
|
If non-empty, restrict to this path group. Default: all. |
|
|
|
If non-empty, restrict to this clock. Default: all. |
Response (JSON):
{
"bins": [{"lower": -1.0, "upper": 0.0, "count": 12, "negative": true}, ...],
"unconstrained_count": 5,
"total_endpoints": 1234,
"time_unit": "ns"
}
chart_filters#
List the path groups and clocks available for the slack histogram filter dropdowns. No request fields.
Response (JSON):
{"path_groups": ["**default**", ...], "clocks": ["clk1", "clk2", ...]}
clock_tree#
Compute the clock tree (CTS view).
No request fields.
Response (JSON):
{
"clocks": [
{
"name": "clk",
"min_arrival": 0.0,
"max_arrival": 0.5,
"time_unit": "ns",
"nodes": [
{"id": 0, "parent_id": -1, "name": "...", "pin_name": "...",
"type": "root" | "buffer" | "inverter" | "clock_gate" | "register" | "macro" | "unknown",
"arrival": 0.0, "delay": 0.0, "fanout": 0, "level": 0,
"dbu_x": 0, "dbu_y": 0},
...
]
}
]
}
clock_tree_highlight#
Highlight a single clock-tree node’s instance in the layout.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Empty string clears the highlight. |
Response (JSON): {"ok": true}.
Module hierarchy#
module_hierarchy#
Return the module hierarchy tree (left sidebar).
No request fields.
Response (JSON):
{
"nodes": [
{
"id": 0, "parent_id": -1,
"inst_name": "<top>", "module_name": "...",
"insts": 1234, "macros": 0, "modules": 5,
"area": 1.23,
"local_insts": 100, "local_macros": 0, "local_modules": 5,
"node_kind": 1, // present when not kModule (kLeafGroup=1, kTypeGroup=2, kInstance=3)
"odb_id": 42, // present only on kModule nodes
"color": [r, g, b] // present only on kModule nodes
},
...
]
}
set_module_colors#
Update the per-module color override map (for the _modules tile
layer).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Custom delimited form: |
Response (JSON): {"ok": 1, "count": <updated module count>}.
set_focus_nets#
Add or remove a net from the focus-nets set (route-tracing overlay).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
|
|
|
✓ |
Net name for |
Response (JSON): {"ok": 1, "count": <focus-net count>}.
set_route_guides#
Same shape as set_focus_nets but updates the route-guides overlay.
Heat maps#
heatmaps#
Return metadata for every registered heat map.
No request fields.
Response (JSON):
{
"active": "Pin", // empty when none active
"heatmaps": [
{
"name": "Pin",
"title": "Pin Density",
"active": true,
"settings_group": "Density",
"has_data": true,
"can_adjust_grid": true,
"show_numbers": false,
"show_legend": true,
"supports_numbers": true,
"units": "/um²",
"display_range_increment": 1.0,
"display_min": 0.0, "display_max": 100.0,
"display_min_limit": 0.0, "display_max_limit": 100.0,
"draw_below_min": true, "draw_above_max": true,
"log_scale": false, "reverse_log": false,
"grid_x": 10.0, "grid_y": 10.0,
"grid_min": 1.0, "grid_max": 1000.0,
"alpha": 150, "alpha_min": 0, "alpha_max": 255,
"bounds": [xMin, yMin, xMax, yMax],
"options": [...], // source-specific extra settings
"legend": [{"value": "5.0", "color": [r, g, b, a]}, ...]
},
...
]
}
set_active_heatmap#
Activate a heat map (or none).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Empty string deactivates the current. |
Response (JSON): same shape as heatmaps.
set_heatmap#
Update one setting on one heat map.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Heat-map name (e.g. |
|
|
✓ |
Setting key (e.g. |
|
|
✓ when |
Type must match the setting’s variant slot, with one tolerance: int-typed settings accept JSON doubles and round (the JS frontend always uses |
Response (JSON): same shape as heatmaps.
heatmap_tile#
Render one tile for the active heat map (or a specified one).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Heat-map name; empty string ⇒ use the active heat map. |
|
|
✓ |
Tile zoom. |
|
|
✓ |
Tile column. |
|
|
✓ |
Tile row. |
Response: PNG (frame type 1), or frame type 3 with no body when
the heat map has no populated bin in this tile.
DRC#
drc_categories#
List top-level DRC categories.
No request fields.
Response (JSON):
{
"categories": [
{"name": "DRC", "count": 42, "description": "...", "source": "..."},
...
]
}
description and source are present only when the category provides
them.
drc_markers#
Drill into one DRC category and return its subcategories + markers.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Top-level category name. Empty string clears the active category and returns |
Response (JSON):
{
"name": "DRC",
"total_count": 42,
"subcategories": [
{
"name": "Subcat",
"count": 3,
"subcategories": [...],
"markers": [
{
"id": 1,
"index": 1,
"name": "...",
"visited": false, "visible": true, "waived": false,
"bbox": [xMin, yMin, xMax, yMax],
"layer": "metal1", // optional
"comment": "rule X", // optional
"sources": [{"type": "Net" | "Inst" | "ITerm" | "BTerm" | "Object", "name": "..."}, ...]
},
...
]
},
...
]
}
When the named category isn’t found, the response is
{"error": "Category not found: <name>"}.
drc_load_report#
Load DRC markers from a .rpt, .drc, or .json file on disk.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Server-side filesystem path. Format inferred from extension. |
Response (JSON): {"ok": 1, "category": "DRC", "count": 42} on
success or {"ok": 0, "error": "..."} on no-violations / failure.
drc_update_marker#
Toggle one marker’s visited or visible flag.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Marker id from |
|
|
✓ |
|
|
|
✓ |
New value. |
Response (JSON): {"ok": 1, "id": <id>, "field": <field>, "value": <bool>}.
drc_update_category_visibility#
Bulk-set every marker in a category to a single visible value.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Top-level name. |
|
|
✓ |
New visibility. |
Response (JSON): {"ok": 1, "category": "...", "visible": <bool>, "count": <updated>}.
drc_highlight#
Center the layout viewport on a marker and set the highlight rect.
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Marker id; pass |
Response (JSON): on hit:
{"ok": 1, "bbox": [xMin, yMin, xMax, yMax], "name": "...", "visited": true, "layer": "metal1"}
layer is omitted when the marker has no tech layer. On miss (or
explicit clear with -1): {"ok": 0}.
Files#
list_dir#
Server-side directory listing (used by the file-open dialog).
Field |
Type |
Required |
Description |
|---|---|---|---|
|
|
✓ |
Absolute path. Empty string ⇒ current working directory of the server. |
Response (JSON):
{
"path": "/canonical/absolute",
"parent": "/canonical",
"entries": [
{"name": "subdir", "is_dir": true},
{"name": "report.rpt","is_dir": false, "size": 12345},
...
]
}
Hidden entries (.-prefixed) and unreadable entries are skipped.
Directories are listed before files; both groups sort alphabetically.
Debug graphics#
debug_continue#
Resume execution after a debug-pause hook (gui::Gui::pause() etc.).
Inline-dispatched (does not enter the worker thread pool). No request
fields.
Response (JSON): {"ok":1}.
debug_charts#
Snapshot the registered debug charts. Inline-dispatched. No request fields.
Response (JSON):
{
"charts": [
{
"name": "...",
"x_label": "iteration",
"y_labels": ["loss", "violations"],
"x_format": "{}", "y_formats": ["{:.2e}", "{}"],
"points": [{"x": 0, "ys": [0.5, 12]}, ...]
},
...
]
}
TileVisibility flags#
These flags appear together (typically prefixed with vf in the JS
client) on every request that takes a viewport: tile, select, and
snap. All are bool. Each handler defaults a missing flag to its
struct default — the column below — but the JS reference frontend
always sends the full set.
Field |
Default |
Notes |
|---|---|---|
|
|
Instance categories (Liberty-aware when STA is loaded). |
|
|
Pad sub-types. |
|
|
Physical-only cell sub-types. |
|
|
Std-cell sub-types (need Liberty/STA). |
|
|
By |
|
|
Wires & vias. |
|
|
BTerm shapes & labels. |
|
|
ITerm shapes & labels. |
|
|
dbBlockage / dbObstruction. |
|
|
Enables the row-outline overlay; gates |
|
|
Routing-track overlay. |
|
|
Debug graphics overlay. |
|
|
Per-row-site visibility. Only consulted when |
Error contract#
The server treats wire-protocol violations as recoverable: the offending
request gets a kError response, the rest of the session continues.
Malformed message (invalid JSON, non-object root, missing/wrong-typed
idortype):kErrorbodyMalformed request (missing or invalid id/type).Unknown type:
kErrorbodyUnknown request type: <type>.Handler exception (boost::json
at()on a missing field, type conversion failure, internalruntime_error, …):kErrorbodyserver error: <exception what()>.
Every kError response logs a server-side warning:
[WARNING WEB-0043] request id=<id> type=<type> failed: <body>
type is the original type string from the request when it was
parseable, otherwise unknown. The full request payload is not
logged; clients should reproduce locally if a payload is needed for
diagnosis.
The reference JS client surfaces kError responses as a Promise
rejection through WebSocketManager.request(...). Custom clients
should handle frame type 2 similarly.