Embeds now report content height to the parent frame using a ResizeObserver on document.body. The EmbedDialog snippet includes a listener script that adjusts the iframe height on each resize event. Documented the spicebook-resize protocol in llms.txt.
624 lines
14 KiB
Plaintext
624 lines
14 KiB
Plaintext
# SpiceBook
|
|
|
|
> Notebook interface for SPICE circuit simulation. Create, edit, and run SPICE netlists in a cell-based notebook UI with waveform visualization and schematic generation. Supports ngspice and LTspice engines.
|
|
|
|
Base URL: `https://spicebook.warehack.ing`
|
|
|
|
## API Reference
|
|
|
|
All endpoints accept and return JSON unless noted otherwise. Prefix all paths with the base URL.
|
|
|
|
---
|
|
|
|
### Notebooks
|
|
|
|
#### List notebooks
|
|
|
|
```
|
|
GET /api/notebooks
|
|
```
|
|
|
|
**Response** `200`
|
|
```json
|
|
[
|
|
{
|
|
"id": "rc-low-pass-a1b2c3d4",
|
|
"title": "RC Low-Pass Filter",
|
|
"engine": "ngspice",
|
|
"tags": ["filter", "rc"],
|
|
"cell_count": 3,
|
|
"modified": "2026-02-13T18:30:00+00:00"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### Create notebook
|
|
|
|
```
|
|
POST /api/notebooks
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"title": "My Circuit",
|
|
"engine": "ngspice"
|
|
}
|
|
```
|
|
|
|
Both fields are optional. Defaults: title = `"Untitled Notebook"`, engine = `"ngspice"`. Supported engines: `"ngspice"`, `"ltspice"`.
|
|
|
|
**Response** `201`
|
|
```json
|
|
{
|
|
"id": "my-circuit-f8e2a91b",
|
|
"spicebook_version": "2026-02-13",
|
|
"metadata": {
|
|
"title": "My Circuit",
|
|
"engine": "ngspice",
|
|
"tags": [],
|
|
"created": "2026-02-13T18:30:00+00:00",
|
|
"modified": "2026-02-13T18:30:00+00:00"
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-a1b2c3d4e5f6",
|
|
"type": "markdown",
|
|
"source": "# My Circuit\n\nAdd SPICE cells below to begin simulating.",
|
|
"outputs": []
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Get notebook
|
|
|
|
```
|
|
GET /api/notebooks/{notebook_id}
|
|
```
|
|
|
|
**Response** `200` — Full `Notebook` object (same shape as create response, without `id` wrapper).
|
|
|
|
#### Update notebook
|
|
|
|
```
|
|
PUT /api/notebooks/{notebook_id}
|
|
```
|
|
|
|
**Request body** — Full `Notebook` object (replaces entire notebook).
|
|
|
|
**Response** `200` — The saved `Notebook`.
|
|
|
|
#### Delete notebook
|
|
|
|
```
|
|
DELETE /api/notebooks/{notebook_id}
|
|
```
|
|
|
|
**Response** `204` — No content. Only user-created notebooks can be deleted.
|
|
|
|
---
|
|
|
|
### Cells
|
|
|
|
Cells are ordered elements within a notebook. Each cell has a `type` (`markdown`, `spice`, `python`, `schematic`) and `source` (text content).
|
|
|
|
#### Add cell
|
|
|
|
```
|
|
POST /api/notebooks/{notebook_id}/cells
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"type": "spice",
|
|
"source": "V1 1 0 DC 5\nR1 1 0 1k\n.op\n.end",
|
|
"after_cell_id": "cell-a1b2c3d4e5f6"
|
|
}
|
|
```
|
|
|
|
`after_cell_id` is optional — omit to append at end.
|
|
|
|
**Response** `201`
|
|
```json
|
|
{
|
|
"id": "cell-b2c3d4e5f6a7",
|
|
"type": "spice",
|
|
"source": "V1 1 0 DC 5\nR1 1 0 1k\n.op\n.end",
|
|
"outputs": []
|
|
}
|
|
```
|
|
|
|
#### Update cell
|
|
|
|
```
|
|
PUT /api/notebooks/{notebook_id}/cells/{cell_id}
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"source": "V1 1 0 DC 10\nR1 1 0 2k\n.op\n.end",
|
|
"type": "spice"
|
|
}
|
|
```
|
|
|
|
Both fields optional — only provided fields are updated.
|
|
|
|
**Response** `200` — Updated `Cell`.
|
|
|
|
#### Delete cell
|
|
|
|
```
|
|
DELETE /api/notebooks/{notebook_id}/cells/{cell_id}
|
|
```
|
|
|
|
**Response** `204`
|
|
|
|
#### Reorder cells
|
|
|
|
```
|
|
PUT /api/notebooks/{notebook_id}/cells/reorder
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"cell_ids": ["cell-b2c3d4e5f6a7", "cell-a1b2c3d4e5f6"]
|
|
}
|
|
```
|
|
|
|
Must include every cell ID exactly once.
|
|
|
|
**Response** `200` — Array of `Cell` objects in new order.
|
|
|
|
---
|
|
|
|
### Simulation
|
|
|
|
#### Run standalone simulation
|
|
|
|
```
|
|
POST /api/simulate
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"netlist": "V1 1 0 DC 5\nR1 1 2 1k\nR2 2 0 2k\n.op\n.end",
|
|
"engine": "ngspice"
|
|
}
|
|
```
|
|
|
|
**Response** `200`
|
|
```json
|
|
{
|
|
"success": true,
|
|
"waveform": {
|
|
"variables": [
|
|
{"name": "v(1)", "type": "voltage"},
|
|
{"name": "v(2)", "type": "voltage"}
|
|
],
|
|
"points": 1,
|
|
"x_data": [0.0],
|
|
"y_data": {"v(1)": [5.0], "v(2)": [3.333]},
|
|
"x_type": "time",
|
|
"is_complex": false,
|
|
"y_magnitude_db": null,
|
|
"y_phase_deg": null
|
|
},
|
|
"log": "ngspice output...",
|
|
"error": null,
|
|
"elapsed_seconds": 0.42
|
|
}
|
|
```
|
|
|
|
#### Run cell in notebook
|
|
|
|
```
|
|
POST /api/notebooks/{notebook_id}/cells/{cell_id}/run
|
|
```
|
|
|
|
No request body — uses the cell's `source` as the netlist and the notebook's `engine`.
|
|
|
|
**Response** `200` — Same `SimulationResponse` shape. The cell's outputs are updated in the saved notebook.
|
|
|
|
---
|
|
|
|
### Schematics
|
|
|
|
#### Generate schematic from cell
|
|
|
|
```
|
|
POST /api/notebooks/{notebook_id}/cells/{cell_id}/schematic
|
|
```
|
|
|
|
No request body. Cell must be type `spice`.
|
|
|
|
**Response** `200`
|
|
```json
|
|
{
|
|
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...>...</svg>",
|
|
"success": true,
|
|
"error": null,
|
|
"component_map": {"R1": "resistor", "V1": "voltage_source"}
|
|
}
|
|
```
|
|
|
|
**Color-coded resistors**: Resistors with parseable values between 0.01 and 1e9 ohms render as IEEE zigzag symbols with segments colored according to the standard 4-band resistor color code. A 10k resistor shows Brown-Black-Orange-Gold bands directly on the zigzag. The entry/exit half-segments and the gap before the tolerance band use the default wire color, mimicking the physical spacing that indicates reading direction. Parametric values (e.g. `{R_val}`) and out-of-range resistances fall back to a standard monochrome zigzag.
|
|
|
|
Band-to-segment mapping (7 zigzag sub-segments):
|
|
- Seg 0 (entry): wire color
|
|
- Seg 1: 1st significant digit
|
|
- Seg 2: 2nd significant digit
|
|
- Seg 3: multiplier
|
|
- Seg 4: gap (wire color, mimics physical spacing)
|
|
- Seg 5: tolerance
|
|
- Seg 6 (exit): wire color
|
|
|
|
---
|
|
|
|
### Waveforms
|
|
|
|
#### Generate SVG plot (JSON-wrapped)
|
|
|
|
```
|
|
POST /api/waveforms/svg
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"waveform": { "...WaveformData from simulation response..." },
|
|
"title": "Output Voltage",
|
|
"width": 800,
|
|
"height": 500,
|
|
"signals": ["v(2)"]
|
|
}
|
|
```
|
|
|
|
`signals` is optional — omit to plot all signals. `width`, `height`, `title` have defaults.
|
|
|
|
**Response** `200`
|
|
```json
|
|
{
|
|
"svg": "<svg ...>...</svg>"
|
|
}
|
|
```
|
|
|
|
#### Generate SVG plot (raw)
|
|
|
|
```
|
|
POST /api/waveforms/svg/raw
|
|
```
|
|
|
|
Same request body as above.
|
|
|
|
**Response** `200` with `Content-Type: image/svg+xml` — raw SVG string.
|
|
|
|
---
|
|
|
|
### Compose (convenience)
|
|
|
|
Create a fully-populated notebook with multiple cells in a single call.
|
|
|
|
#### Compose notebook
|
|
|
|
```
|
|
POST /api/notebooks/compose
|
|
```
|
|
|
|
**Request body**
|
|
```json
|
|
{
|
|
"title": "RC Low-Pass Filter",
|
|
"engine": "ngspice",
|
|
"tags": ["filter", "rc", "analog"],
|
|
"cells": [
|
|
{
|
|
"type": "markdown",
|
|
"source": "# RC Low-Pass Filter\n\nA simple first-order low-pass filter."
|
|
},
|
|
{
|
|
"type": "spice",
|
|
"source": "V1 in 0 AC 1\nR1 in out 1k\nC1 out 0 1u\n.ac dec 100 1 1meg\n.end"
|
|
},
|
|
{
|
|
"type": "markdown",
|
|
"source": "## Analysis\n\nThe -3dB cutoff is at f = 1/(2*pi*R*C) = 159 Hz."
|
|
}
|
|
],
|
|
"run": false
|
|
}
|
|
```
|
|
|
|
| Field | Type | Default | Description |
|
|
|----------|-----------------|----------------------|------------------------------------------------------|
|
|
| title | string | "Untitled Notebook" | Notebook title |
|
|
| engine | string | "ngspice" | Simulation engine |
|
|
| tags | list of strings | [] | Searchable tags |
|
|
| cells | list of objects | (required) | Cells to create, each with `type` and `source` |
|
|
| run | bool | false | If true, execute each SPICE cell after creation |
|
|
|
|
**Response** `201` — Same shape as `POST /api/notebooks` (notebook with `id` at top level).
|
|
|
|
When `run` is `true`, each `spice` cell is executed sequentially using the notebook's engine. Simulation results are stored in each cell's `outputs` array. Non-SPICE cells are unaffected.
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
### CellType (enum)
|
|
|
|
`"markdown"` | `"spice"` | `"python"` | `"schematic"`
|
|
|
|
### Cell
|
|
|
|
```json
|
|
{
|
|
"id": "cell-a1b2c3d4e5f6",
|
|
"type": "spice",
|
|
"source": "V1 1 0 DC 5\n.op\n.end",
|
|
"outputs": [
|
|
{
|
|
"output_type": "simulation_result",
|
|
"data": { "success": true, "waveform": {...}, "log": "...", "error": null, "elapsed_seconds": 0.3 },
|
|
"timestamp": "2026-02-13T18:35:00+00:00"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Notebook
|
|
|
|
```json
|
|
{
|
|
"spicebook_version": "2026-02-13",
|
|
"metadata": {
|
|
"title": "string",
|
|
"engine": "ngspice",
|
|
"tags": ["string"],
|
|
"created": "ISO-8601 datetime",
|
|
"modified": "ISO-8601 datetime"
|
|
},
|
|
"cells": [Cell, ...]
|
|
}
|
|
```
|
|
|
|
### SimulationResponse
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"waveform": WaveformData | null,
|
|
"log": "string",
|
|
"error": "string | null",
|
|
"elapsed_seconds": 0.0
|
|
}
|
|
```
|
|
|
|
### WaveformData
|
|
|
|
```json
|
|
{
|
|
"variables": [{"name": "v(out)", "type": "voltage"}],
|
|
"points": 100,
|
|
"x_data": [0.0, 0.001, ...],
|
|
"y_data": {"v(out)": [0.0, 0.5, ...]},
|
|
"x_type": "time",
|
|
"is_complex": false,
|
|
"y_magnitude_db": null,
|
|
"y_phase_deg": null
|
|
}
|
|
```
|
|
|
|
For AC analysis (`is_complex: true`), `y_magnitude_db` and `y_phase_deg` contain per-signal arrays. `x_type` will be `"frequency"` and `x_data` holds frequency values in Hz.
|
|
|
|
---
|
|
|
|
## SPICE Netlist Primer
|
|
|
|
SpiceBook supports **ngspice** and **LTspice** as simulation engines. Netlists are plain text describing a circuit and the analysis to perform.
|
|
|
|
### Basic structure
|
|
|
|
```spice
|
|
* Title line (optional comment)
|
|
V1 node_pos node_neg DC 5 * DC voltage source
|
|
R1 node_a node_b 1k * Resistor: 1 kilo-ohm
|
|
C1 node_a node_b 100n * Capacitor: 100 nanofarads
|
|
L1 node_a node_b 10m * Inductor: 10 millihenrys
|
|
|
|
.analysis_type parameters
|
|
.end
|
|
```
|
|
|
|
Node `0` is always ground.
|
|
|
|
### Supported analysis types
|
|
|
|
| Command | Description | Example |
|
|
|---------|-------------|---------|
|
|
| `.op` | DC operating point | `.op` |
|
|
| `.dc` | DC sweep | `.dc V1 0 5 0.1` |
|
|
| `.tran` | Transient (time-domain) | `.tran 1u 10m` |
|
|
| `.ac` | AC frequency sweep | `.ac dec 100 1 1meg` |
|
|
|
|
### Engineering suffixes
|
|
|
|
| Suffix | Multiplier | Example |
|
|
|--------|-----------|---------|
|
|
| T | 10^12 | `1T` = 1 tera |
|
|
| G | 10^9 | `2.2G` = 2.2 giga |
|
|
| meg | 10^6 | `1meg` = 1 mega (note: not `M` — that's milli in SPICE) |
|
|
| k | 10^3 | `4.7k` = 4700 |
|
|
| m | 10^-3 | `10m` = 0.01 |
|
|
| u | 10^-6 | `100u` = 100 micro |
|
|
| n | 10^-9 | `47n` = 47 nano |
|
|
| p | 10^-12 | `10p` = 10 pico |
|
|
| f | 10^-15 | `1f` = 1 femto |
|
|
|
|
### Common sources
|
|
|
|
```spice
|
|
V1 node+ node- DC 5 * DC voltage
|
|
V2 node+ node- AC 1 * AC source (for .ac analysis)
|
|
V3 node+ node- PULSE(0 5 0 1n 1n 5u 10u) * Pulse source
|
|
I1 node+ node- DC 1m * DC current source
|
|
```
|
|
|
|
---
|
|
|
|
## Example Workflow
|
|
|
|
### 1. Create a notebook with the compose endpoint
|
|
|
|
```bash
|
|
curl -X POST https://spicebook.warehack.ing/api/notebooks/compose \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"title": "Voltage Divider",
|
|
"engine": "ngspice",
|
|
"tags": ["resistive", "dc", "beginner"],
|
|
"cells": [
|
|
{
|
|
"type": "markdown",
|
|
"source": "# Voltage Divider\n\nTwo resistors divide a 5V supply."
|
|
},
|
|
{
|
|
"type": "spice",
|
|
"source": "V1 1 0 DC 5\nR1 1 2 1k\nR2 2 0 2k\n.op\n.end"
|
|
},
|
|
{
|
|
"type": "markdown",
|
|
"source": "## Expected Result\n\nV(2) = 5 * 2k / (1k + 2k) = 3.333V"
|
|
}
|
|
],
|
|
"run": true
|
|
}'
|
|
```
|
|
|
|
Response includes the notebook `id` and all cells. Because `run: true`, the SPICE cell's `outputs` array will contain the simulation result with `v(2) = 3.333`.
|
|
|
|
### 2. View the notebook in the browser
|
|
|
|
Open `https://spicebook.warehack.ing/notebook/{id}` — the notebook renders with the markdown cells formatted and the SPICE cell showing its simulation output.
|
|
|
|
### 3. Add another cell
|
|
|
|
```bash
|
|
curl -X POST https://spicebook.warehack.ing/api/notebooks/{id}/cells \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"type": "spice",
|
|
"source": "V1 1 0 DC 5\nR1 1 2 1k\nR2 2 0 2k\n.dc V1 0 10 0.5\n.end"
|
|
}'
|
|
```
|
|
|
|
### 4. Run the new cell
|
|
|
|
```bash
|
|
curl -X POST https://spicebook.warehack.ing/api/notebooks/{id}/cells/{cell_id}/run
|
|
```
|
|
|
|
### 5. Generate a schematic
|
|
|
|
```bash
|
|
curl -X POST https://spicebook.warehack.ing/api/notebooks/{id}/cells/{cell_id}/schematic
|
|
```
|
|
|
|
### 6. Visualize waveform data
|
|
|
|
Take the `waveform` from the simulation response and POST it to the waveform endpoint:
|
|
|
|
```bash
|
|
curl -X POST https://spicebook.warehack.ing/api/waveforms/svg/raw \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"waveform": { ...waveform object from simulation response... },
|
|
"title": "DC Sweep",
|
|
"signals": ["v(2)"]
|
|
}' \
|
|
-o plot.svg
|
|
```
|
|
|
|
---
|
|
|
|
## Health Check
|
|
|
|
```
|
|
GET /health
|
|
```
|
|
|
|
**Response** `200`
|
|
```json
|
|
{"status": "ok", "version": "2026.02.13"}
|
|
```
|
|
|
|
---
|
|
|
|
## Embedding
|
|
|
|
Any notebook can be embedded on a third-party page via iframe. No API key or authentication is required.
|
|
|
|
### Embed URL
|
|
|
|
```
|
|
https://spicebook.warehack.ing/embed/{notebook_id}?theme=dark
|
|
```
|
|
|
|
| Parameter | Values | Default | Description |
|
|
|-----------|--------|---------|-------------|
|
|
| theme | `dark`, `light` | `dark` | Initial color theme |
|
|
|
|
### iframe snippet
|
|
|
|
```html
|
|
<iframe
|
|
src="https://spicebook.warehack.ing/embed/{notebook_id}?theme=dark"
|
|
width="100%" height="600"
|
|
style="border: 1px solid #334155; border-radius: 8px;"
|
|
allow="clipboard-write"
|
|
></iframe>
|
|
```
|
|
|
|
The embed renders the notebook read-only with working simulation — viewers can run SPICE cells and see waveform results.
|
|
|
|
### Theme control via postMessage
|
|
|
|
The parent page can switch the embed's theme at runtime:
|
|
|
|
```javascript
|
|
iframe.contentWindow.postMessage(
|
|
{ type: 'spicebook-theme', theme: 'light' },
|
|
'*'
|
|
);
|
|
```
|
|
|
|
Accepted values for `theme`: `"dark"`, `"light"`. Other messages are ignored.
|
|
|
|
### Auto-resize via postMessage
|
|
|
|
The embed reports its content height to the parent page whenever the layout changes (initial render, simulation results, expanded cells). The parent listens for `spicebook-resize` messages and updates the iframe height:
|
|
|
|
```javascript
|
|
window.addEventListener('message', function(e) {
|
|
if (e.data && e.data.type === 'spicebook-resize') {
|
|
document.getElementById('spicebook-{notebook_id}').style.height = e.data.height + 'px';
|
|
}
|
|
});
|
|
```
|
|
|
|
The message payload:
|
|
|
|
```json
|
|
{ "type": "spicebook-resize", "height": 1842 }
|
|
```
|
|
|
|
`height` is `document.documentElement.scrollHeight` in pixels. The embed sends this message on every `ResizeObserver` callback, deduplicated to only fire when height actually changes. The **Embed** button's generated snippet includes this listener automatically.
|
|
|
|
### Discovering the embed snippet
|
|
|
|
In the notebook editor UI, the **Embed** button in the toolbar opens a popover with a ready-to-copy iframe snippet (including auto-resize listener) and a theme toggle.
|