The host agent listens on port 8090 by default. Responses are JSON, use
Cache-Control: no-store, and include permissive CORS headers for local lab
development.
Warning
The built-in server is intended for a trusted lab network. Put an authenticated TLS reverse proxy in front of it before crossing a security boundary.
{ "ok": true, "timestamp": 1784327816.56 }Returns LinuxPTP version, NIC/PHC counts, namespaces, active timing processes, and whether privileged lifecycle control is installed.
{
"hostname": "PTPBox",
"linuxptp": "4.4",
"interfaces": 16,
"ptp_interfaces": 16,
"namespaces": [],
"processes": [],
"running": false,
"pps": {
"enabled": false,
"running": false,
"source": "BC1",
"sinks": [],
"servo": "pi",
"nodes": {
"BC1": {
"role": "source",
"state": "ready",
"capable": true,
"device": "/dev/ptp1",
"pin": {
"name": "mlx5_pps0",
"function": "none",
"channel": 0
}
}
}
},
"advanced_capabilities": {
"dpll": false,
"synce": false,
"path_monitor": true,
"temperature": true,
"pps_common_edge": false
},
"profile_compliance": {
"profile": "G.8275.1 Telecom",
"compliant": true,
"certification": false
},
"observer_only": false,
"agent_version": "2.5.0"
}pps.nodes is hardware-backed. It combines the staged role with the PHC's
sysfs PPS capabilities, current pin function, managed ts2phc process state,
device, connector, and channel. Node state is one of ready, active,
starting, stopped, external, or unavailable.
{
"interfaces": [
{
"name": "enp25s0f0np0",
"state": "UP",
"carrier": true,
"speed_mbps": 100000,
"mac": "00:00:00:00:00:00",
"driver": "mlx5_core",
"bus": "0000:19:00.0",
"phc": "ptp0",
"hardware_timestamping": true,
"namespace": "BC1",
"assignment": "BC1 / INACTIVE IN"
}
],
"timestamp": 1784327816.62
}The lifecycle controller records timing-port metadata after moving each adapter into its namespace. The unprivileged agent merges that snapshot with the live host-namespace management ports, so the response and status counts still cover all physical interfaces while the cascade is running.
Returns read-only PHC comparisons at the applied PTP Sync cadence: 0.5, 1, 2,
4, or 8 Hz. The sample_rate_hz response field reports the active cadence.
The first mapped NIC is the reference. Every PHC is cross timestamped against CLOCK_MONOTONIC_RAW using
the shortest of nine kernel-bracketed samples. BC1 is measured before and after
the targets and interpolated to each target's measurement epoch. offset_ns is
the cumulative difference from BC1; previous_hop_offset_ns is the delta from
the preceding NIC. The sampler never sets, steps, or adjusts a clock. Clients
can use since for incremental updates; the Observatory polls this lightweight
endpoint at the reported cadence while full LinuxPTP parsing stays on a slower
supervisory path.
{
"reference": "BC1",
"reference_phc": "ptp2",
"method": "common-system cross timestamps with interpolated BC1 reference",
"sample_rate_hz": 8.0,
"raw": true,
"smoothing": "none",
"clocks": [
{
"id": "BC2",
"phc": "ptp1",
"measurement": {
"offset_ns": 31.4,
"previous_hop_offset_ns": 31.4,
"read_span_ns": 710,
"comparison_uncertainty_ns": 715,
"cross_timestamp_method": "PTP_SYS_OFFSET_EXTENDED(CLOCK_MONOTONIC_RAW), best of 9",
"observed_at": 1784327800.0,
"raw": true,
"valid": true,
"error": null
}
}
]
}The agent reads raw client-side LinuxPTP logs from PTPBOX_LOG_DIR (normally
/var/log/ptpbox) with a legacy fallback to PTPBOX_ROOT/BC*. It extracts
every offset, frequency adjustment, mean path delay, servo state, and LinuxPTP
source timestamp without smoothing or interpolation. The response also embeds
the direct PHC measurement and incremental phc_samples for each clock so the
UI can plot clock differences independently from servo diagnostics.
Query parameters:
history: requested window in seconds, clamped to 5–900;since: return only samples newer than this Unix timestamp;limit: maximum raw samples per clock, capped at 4096.
{
"timestamp": 1784327816.64,
"mode": "live",
"phc_mode": "live",
"measurement_source": "kernel cross-timestamped PHC comparison",
"measured_clocks": 1,
"fresh_clocks": 1,
"raw": true,
"smoothing": "none",
"clocks": [
{
"id": "BC2",
"role": "boundary",
"ingress": "enp26s0f0np0",
"egress": "enp26s0f1np1",
"logs": 2,
"window_sample_count": 1920,
"window_locked_sample_count": 1912,
"rms_ns": 18.7,
"measurement_phc": "ptp1",
"phc_rms_ns": 45.1,
"phc_measurement": {
"offset_ns": 42,
"previous_hop_offset_ns": 42,
"phc": "ptp1",
"observed_at": 1784327800.0,
"raw": true,
"valid": true
},
"phc_samples": [],
"measurement": {
"offset_ns": 42,
"frequency_ppb": -12.4,
"mean_path_delay_ns": 241,
"servo_state": "s2",
"source": "BC2-OC.log",
"observed_at": 1784327800.0,
"raw": true,
"valid": true,
"validation_error": null
},
"samples": []
}
]
}mode describes LinuxPTP log freshness; phc_mode independently describes
direct PHC-read freshness. samples and phc_samples can be empty on an
incremental request even while their latest measurements and window statistics
remain populated.
The response also includes servo_control.nodes. Each downstream node reports
its selected type, whether discipline is enabled, its mode (active or
holdover), and the Unix timestamp at which holdover began.
For an active Kalman clock, the offset and path delay remain the untouched
LinuxPTP observation. measurement.frequency_ppb reports the correction that
PTPBox actually applied, measurement.linuxptp_frequency_ppb preserves
LinuxPTP's non-disciplining rate estimate, and control_source is
ptpbox-kalman.
The raw payload preserves invalid samples but marks them valid: false.
Non-finite offsets and absolute path-delay estimates above 1 ms are rejected.
A small negative LinuxPTP path-delay estimate is retained during acquisition:
it can result from inter-clock phase being larger than the physical link delay
and is not, by itself, evidence of a driver timestamp failure. Invalid samples
are counted separately and excluded from RMS and charts; their original values
remain available through the API.
Servo rms_ns uses only valid locked (s2) samples. Acquisition samples remain
in samples but cannot inflate the steady-state stability metric.
Returns the staged configuration or safe defaults.
Validates and atomically stages a complete configuration document.
{
"profile": "G.8275.1 Telecom",
"domain": 24,
"transport": "L2",
"delay_mechanism": "E2E",
"log_sync_interval": 0,
"two_step": true,
"hardware_timestamping": true,
"servo": {
"type": "pi",
"kp": 0.7,
"ki": 0.3,
"step_threshold_ns": 0,
"first_step_threshold_ns": 20000,
"sanity_freq_limit_ppb": 200000,
"kalman": {
"measurement_noise_ns": 200.0,
"process_noise_ppb": 10.0,
"phase_time_constant_s": 4.0,
"innovation_gate_sigma": 6.0,
"drift_noise_ppb_s2": 0.05
}
},
"security": {
"authentication": {
"enabled": false,
"spp": 0,
"active_key_id": 1,
"sa_file": "/etc/linuxptp/ptpbox-sa.cfg",
"allow_unauth": 0
}
},
"pps": {
"enabled": false,
"source": "BC1",
"sinks": ["BC2", "BC3", "BC4", "BC5", "BC6", "BC7"],
"output_pin": 0,
"input_pin": 0,
"channel": 0,
"polarity": "rising",
"pulse_width_ns": 100000000,
"perout_phase_ns": 0,
"extts_correction_ns": 0,
"comparison": {
"enabled": false,
"measure_only": true,
"reference": "BC2",
"history": 256
},
"ts2phc": {
"servo": "pi",
"kp": 0.7,
"ki": 0.3,
"step_threshold_ns": 0,
"first_step_threshold_ns": 20000,
"holdover_seconds": 0,
"stable_threshold_ns": 100,
"stable_samples": 10,
"logging_level": 6
}
}
}Success is 200 with staged: true. Validation failures return 422 with a
details array.
log_sync_interval is the signed base-2 exponent defined by IEEE 1588 and used
directly by LinuxPTP: the effective Sync frequency is 2^-log_sync_interval
hertz. PTPBox accepts 1 through -3, corresponding to 0.5, 1, 2, 4, and
8 Hz. The Observatory's operator slider moves in 0.5 Hz steps from 0.5 through
10 Hz, but always displays and applies the nearest protocol-representable rate;
it never labels an unrepresentable request as an on-wire frequency.
Staging does not alter a running process. The Observatory's guarded “Apply to
cascade” flow follows a successful stage with POST /api/control using the
restart action so every managed ptp4l instance reads the same new interval.
If PPS is enabled, the controller first validates periodic-output,
external-timestamp, programmable-pin, channel, and /dev/ptp* availability,
then generates /etc/linuxptp/ptpbox-ts2phc.conf and starts one tracked
ts2phc process. With PPS disabled, it neither changes PHC pins nor starts
ts2phc.
Profile presets validate the transport, delay mechanism, domain range, and
two-step compatibility that PTPBox implements. The API sets
certification: false: passing these checks is not a claim of complete
conformance testing. The G.8275.1 preset uses domains 24–43, G.8275.2 uses
44–63, 802.1AS uses domain 0, and the C37.238 preset uses the profile exception
domain 254. LinuxPTP Authentication TLVs require two-step operation and a
root-owned Security Association file below /etc/linuxptp; its key material is
never returned by the API.
Returns the supported on-box servo implementations and the current per-clock
state. Choices are pi, linreg, nullf, kalman, adaptive-kalman, and
imm.
Applies a servo to one downstream clock or every receiver. Setting enabled
to false enters measured holdover: LinuxPTP continues receiving Sync messages
and reporting raw offsets, but the generated configuration uses
free_running 1 so it does not adjust the PHC. The independent PHC comparison
sampler continues at the applied Sync cadence.
{
"target": "BC7",
"enabled": false,
"type": "pi"
}Resume BC7 under the adaptive linear-regression servo with:
{
"target": "BC7",
"enabled": true,
"type": "linreg"
}Run BC7 under the PTPBox Kalman servo with:
{
"target": "BC7",
"enabled": true,
"type": "kalman"
}Every PTPBox Kalman mode uses the raw LinuxPTP master offset as its observation.
ptp4l runs with free_running 1, so it cannot compete with the dedicated
controller for the PHC. Classic kalman estimates phase and frequency.
adaptive-kalman adds oscillator drift and online measurement-noise
adaptation. imm mixes quiet, dynamic, and holdover models and returns the
posterior regime probabilities. All modes gate outliers, re-anchor after a
persistent phase transition, clamp the applied clock_adjtime frequency
correction, and expose estimates, uncertainty, innovations, sample counts,
rejections, correction, and lock state in each clock's kalman object.
Use target: "all" for BC2 through the final OC. Changing the servo requires a
brief restart of only the selected ptp4l instance; the agent and PHC sampler
do not restart. nullf deliberately commands zero frequency correction and is
intended for SyncE-backed diagnostics, not ordinary oscillator discipline.
Returns the persistent holdover session, continuous-lock qualification,
per-node release baselines, raw display series, and all-run metrics. The session
state is one of synchronizing, releasing, holdover, resuming,
completed, aborted, or error.
series[].values_ns is the raw BC1-relative PHC difference minus that node's
pre-release median. The endpoint applies no smoothing. Long runs are uniformly
decimated to at most 1,800 displayed cycles, retain the final cycle, and report
display_stride; the SQLite run and CSV export retain every captured row.
metrics.<node>.drift_ppb is the least-squares slope of time error in ns/s.
Those units are numerically equal to ppb fractional-frequency error.
Arm a run that first restores each node's existing servo, requires continuous
LinuxPTP s2 state plus fresh direct PHC comparisons, then automatically
freezes clock adjustment:
{
"action": "start",
"nodes": ["BC2", "BC3", "BC4", "BC5", "BC6", "BC7"],
"stable_dwell_s": 30,
"stable_threshold_ns": 1000,
"duration_s": 300,
"auto_release": true,
"auto_resume": true
}Any node leaving s2, exceeding the release gate, or losing fresh PHC data
resets the dwell. At release the agent:
- calculates a per-node median from the final qualified PHC window;
- changes selected clocks to LinuxPTP
free_running 1, downstream first; - keeps PTP messages, the agent sampler, and SQLite capture running;
- restores the exact saved servo type when the duration expires.
The other actions are:
{ "action": "release" }
{ "action": "resume" }
{ "action": "abort" }release is accepted only after the stable dwell unless an API client
explicitly sends "force": true. resume seals the run after restoring
synchronization. abort is the safe escape during qualification and also
restores synchronization. A non-holdover experiment already recording causes
start to return 409 rather than silently replacing it.
Returns one aligned analysis snapshot. history is clamped to 30–7200 seconds.
The main objects are:
| Object | Contents |
|---|---|
stability |
Endpoint ADEV, MDEV, HDEV, PDEV, TOTDEV, and Theo1 fractional-frequency curves plus TDEV, MTIE, and TIE RMS nanosecond curves, with τ and usable-term counts |
stability_summary |
Record span, detrended RMS, peak-to-peak phase, frequency bias/drift, minimum ADEV, and explicitly qualified local MDEV noise-slope candidates |
dynamics.transfer_noise |
TIE RMS, FTU, and ADEVS with residual qualification and provenance; direct PHC differences are marked clock-plus-transfer composite |
dynamics.dynamic_stability |
Sliding-window clock ADEV/MDEV and composite or qualified-residual FTU/ADEVS cells |
dynamics.spectral_cascade |
Welch PSD/CSD, per-hop/cumulative gain, coherence, phase, and dominant spatial modes; passive results explicitly set formal_string_stability: false |
dynamics.multiresolution_modes |
Log-frequency coherent-mode bands, energy shares, and spatial loadings |
dynamics.hybrid_servo |
Observed mode shares, transition probabilities, dwell statistics, correction/offset RMS, and local phase poles |
dynamics.estimator_consistency |
Kalman NIS, 95% inclusion, innovation lag-one correlation, acceptance share, and consistency screen |
dynamics.identifiability |
Normalized ARX information eigenvalues, rank, condition number, input variance, and persistent-excitation flag |
dynamics.active_identification |
Instrumental plant/open-loop estimates, S/T/KS, Nyquist points, coherence gates, balanced disk-margin screen, and uncertainty envelope |
dynamics.holdover_risk |
Phase/frequency/drift forecast tube and 5% time-to-mask crossings |
dynamics.clock_decomposition |
N-cornered individual-clock variance screen with independent-holdover eligibility gate |
dynamics.timing_oam |
cTE, dTE, P95/max/peak-to-peak TE, hop accumulation, and operator reference masks |
dynamics.path_regimes |
Nearest-in-time paired Sync/Delay round-trip and directional-imbalance regimes; never labeled calibrated asymmetry |
dynamics.nonlinear |
Bicoherence, delay-embedding Betti curves, predictive variance-reduction links, and multiscale sample entropy |
fusion |
Fused offsets, 1σ uncertainty, residuals, χ², and degrees of freedom |
ensemble |
Covariance-regularized clock weights, virtual offset, and 1σ |
error_budget |
Per-clock components and covariance-aware cascade uncertainty |
temperature_holdover |
Forecast horizon, phase, frequency, and 1σ when sensors are aligned |
system_identification |
ARX coefficients, poles, fit, residual, settling time, unit-circle Bode/Nyquist samples, direct Jury/Schur conditions, and bilinear-equivalent Routh–Hurwitz table |
auto_tune |
Replay-only GP/EI PI recommendation, frontier, candidate counts, and live_changes: 0 |
change_detection |
Bounded BOCPD probability and detected change indices |
recurrence |
Binary recurrence matrix, recurrence rate, determinism, and threshold |
bifurcation |
Offline PI gain-scale sweep with settled extrema, response bands, replay bounds, provenance, and live_changes: 0 |
fractal |
Higuchi trace dimension, embedded Grassberger–Procaccia correlation dimension, MF-DFA spectrum width, shuffled surrogates, fit quality, and live_changes: 0 |
attractor |
AMI-selected delay, false-nearest-neighbor embedding curve, standardized delay coordinates, recurrent-core candidates and coverage, successive-maxima return pairs, local-divergence fit, independent evidence gates, provenance, and live_changes: 0 |
koopman |
Fitted snapshot operator, singular values, residual σ, and amplification label |
capabilities |
Hardware-derived DPLL, SyncE, devlink, temperature, path-monitor, and PPS status |
profiles |
Applied configuration checks; explicitly not standards certification |
experiments |
Recent durable runs and the active run |
analysis_cache |
Cache age, ten-second refresh target, refresh state, and request-coalescing flag |
Calculations report waiting or learning until enough real samples exist.
No research endpoint adjusts a clock. Identical pollers share one heavy
analysis result; after ten seconds, the current result returns immediately
while exactly one background refresh recomputes it.
Adaptive-Kalman and IMM controller state is returned per clock in
GET /api/telemetry under clocks[].kalman, because it is the state of the
actual selected servo rather than a parallel research-only estimate.
Starts or stops one bounded independent multisine on a downstream
Kalman-family servo. Starting requires a currently enabled kalman,
adaptive-kalman, or imm controller.
{
"target": "BC7",
"enabled": true,
"amplitude_ppb": 25,
"duration_s": 180,
"offset_limit_ns": 5000,
"frequencies_hz": [0.01, 0.025, 0.05, 0.1]
}Limits are 0.1–500 ppb, 30–900 seconds, 100–100,000 ns, and one to eight frequencies between 0.002 Hz and 45% of the configured PHC sample rate. The root controller repeats the validation before arming the worker. The worker automatically disables excitation when the duration expires or the raw LinuxPTP master offset crosses the abort limit.
Stop the active instrument with:
{ "target": "BC7", "enabled": false }GET /api/status returns the persisted identification state. Every
instrumented Kalman snapshot records the base correction,
excitation_ppb, applied_correction_ppb, active flag, and safety outcome so
GET /api/research can evaluate coherence without reconstructing the command.
Returns capability-gated hardware truth. refresh=1 bypasses the short cache.
An unavailable DPLL or SyncE API is reported as unsupported with a reason; PTP
lock is never used as a substitute.
Returns the active profile preset, implemented configuration checks, supported
presets, validation scope, and certification: false.
Returns up to 2048 preserved LinuxPTP slave-event-monitor records. A record
contains the hop, Sync and Delay sequence IDs, t1, t2, t3, t4,
correction fields, receipt time, and derived apparent timestamp residuals.
Timestamp values remain decimal strings to avoid JSON floating-point loss.
Sync and Delay sequence IDs are tracked independently. The apparent forward/reverse residual is not labeled path asymmetry because two unsynchronized PHCs contribute twice their phase difference.
In addition to process state, pps.comparison reports the common-edge EXTS
comparator when it is configured. This mode requires one external PPS connected
to at least two PHC input pins and remains measure_only; it does not start a
competing ts2phc servo.
Creates a durable run in PTPBOX_STATE_DIR/experiments.sqlite3, snapshots the
applied configuration, and starts recording every raw PHC comparison in WAL
mode.
{
"type": "step",
"target": "BC7",
"amplitude_ns": 1000,
"duration_s": 120,
"servo": { "kp": 0.7, "ki": 0.3, "step_threshold_ns": 0 }
}Starting a new run completes any currently active run. Hardware stimulus remains a separate, guarded action.
{ "id": "run-20260723-153000" }id is optional; without it the active run is completed.
Returns the active run and the most recent 100 runs with sample/event counts, metadata, captured configuration, and lifecycle timestamps.
Downloads raw samples as CSV. Run IDs are validated and database paths are never accepted from the request.
{
"target": "BC3",
"enabled": true,
"delay_us": 250,
"jitter_us": 50,
"loss_pct": 0,
"duration_s": 30
}The controller applies tc netem only to the declared node's downstream
egress. At least one impairment must be non-zero. Delay and jitter are capped at
one second, loss at 100%, and duration at one hour. The qdisc is removed on
expiry, explicit clear, or cascade stop.
{ "action": "status" }Allowed HTTP lifecycle actions are start, stop, restart, and status.
Servo and fault transitions use their dedicated endpoints so requests are
validated and atomically staged before the internal fixed helper verb is
invoked. The agent invokes the installed helper through sudo -n. Unsupported
actions return 400; missing integration returns 503; lifecycle conflicts
return 409.
Returns the newest-first shared capture manifest, capture count, total stored bytes, and storage provenance. Image bytes are not embedded in this response.
Stores one graph capture. The request contains a PNG data URL plus its title, Observatory section, graph identifier, capture timestamp, pixel dimensions, and optional scalar metadata. The agent verifies the PNG signature, rejects malformed base64, limits decoded images to 8 MiB and 20,000 pixels per dimension, and retains at most 500 captures.
Returns the validated PNG. Add ?download=1 for an attachment response. Capture
identifiers are strict server-generated tokens; filesystem paths are never
accepted.
Deletes the selected image and updates the manifest atomically. Album data is
stored under PTPBOX_ALBUM_DIR, which defaults to
PTPBOX_STATE_DIR/album.
All other GET paths are resolved beneath PTPBOX_WEB_ROOT. Unknown client-side
routes fall back to standalone/index.html. Resolved asset paths are checked
against the web root to prevent path traversal.