Jallarhorn

Jallarhorn documentation

Install Jallarhorn, organize devices by site, configure sensors, wire up routed alerting and escalations, watch uptime and maps, federate across sites, and integrate with your own tooling. Everything is on this page; use the left sidebar to jump.

Downloads

Every download is hosted on jallarhorn.com, signed with our Cosign release key, and ships with an SPDX SBOM. Each archive is the full suite — jallarhorn-control, jallarhorn-sensor, and jallarhorn-farm in one bundle.

PlatformDownloadNotes
Linux (x86-64) jallarhorn_0.2.3_linux_amd64.tar.gz · sig Any glibc-2.31+ distro. systemd units in the archive.
Linux (arm64) jallarhorn_0.2.3_linux_arm64.tar.gz · sig Raspberry Pi, AWS Graviton, etc.
macOS (Apple Silicon) jallarhorn_0.2.3_darwin_arm64.tar.gz · sig macOS 12+. M-series.
macOS (Intel) jallarhorn_0.2.3_darwin_amd64.tar.gz · sig macOS 12+. Intel.
Windows (x86-64) jallarhorn-setup-0.2.3.exe · sig
jallarhorn_0.2.3_windows_amd64.zip · sig
Windows 10/11, Server 2016+. Run the signed jallarhorn-setup-0.2.3.exe to install control + sensor as services, or use the manual ZIP + NSSM path (see Install → Windows).
Docker docker pull jallarhorn.com/jallarhorn-control:0.2.3 Also -sensor, -farm, -license. docker compose up -d brings up the full stack.
Kubernetes Helm chart in helm/jallarhorn/ Deployments for control + sensor, DaemonSet for per-node sensors.

Checksums: SHA256SUMS · signing key: cosign.pub · SBOMs are the *.spdx.sbom.json files alongside each artifact.

Verifying a release

Each artifact has a detached .sig signed with our Cosign release key (public key at jallarhorn.com/cosign.pub — key-based, no third-party transparency log):

curl -O https://jallarhorn.com/cosign.pub
cosign verify-blob \
  --key cosign.pub \
  --signature jallarhorn_0.2.3_linux_amd64.tar.gz.sig \
  --insecure-ignore-tlog \
  jallarhorn_0.2.3_linux_amd64.tar.gz

# Container images:
cosign verify --key cosign.pub jallarhorn.com/jallarhorn-control:0.2.3

Install & configure

Three executables — jallarhorn-control (REST + dashboard + alert engine, port 28090), jallarhorn-sensor (polling agent), and jallarhorn-farm (multi-site federation, port 28092). All three ship in a single archive.

Database requirement

Jallarhorn stores metrics in TimescaleDB, so PostgreSQL 16 with the timescaledb extension is required (the pgcrypto extension is also required — it ships with stock PostgreSQL). Install TimescaleDB per the official guide, then, as a database superuser:

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS timescaledb;

The control binary attempts these itself on boot and applies its own schema migrations automatically — there is nothing to run by hand. On a stock PostgreSQL without TimescaleDB it exits immediately with the exact commands to run.

Option A — Linux one-liner (recommended)

Downloads the latest signed Linux bundle, verifies its SHA-256 checksum, installs the binaries to /opt/jallarhorn, and writes systemd units (the sensor unit is granted CAP_NET_RAW so ICMP ping works). It does not start anything until you supply the database + secrets — the script prints the exact next steps.

curl -fsSL https://jallarhorn.com/install.sh | sudo sh

Option B — Manual Linux tarball

Prefer to inspect first? Grab the archive from the Downloads table (each is the full suite — jallarhorn-control, jallarhorn-sensor, jallarhorn-farm — plus docker-compose.yml and .env.production.example). Verify the signature with cosign (install from sigstore.dev), then extract:

curl -fsSLO https://jallarhorn.com/download/0.2.3/jallarhorn_0.2.3_linux_amd64.tar.gz
curl -fsSLO https://jallarhorn.com/download/0.2.3/jallarhorn_0.2.3_linux_amd64.tar.gz.sig
curl -fsSLO https://jallarhorn.com/cosign.pub
cosign verify-blob --key cosign.pub \
  --signature jallarhorn_0.2.3_linux_amd64.tar.gz.sig \
  --insecure-ignore-tlog jallarhorn_0.2.3_linux_amd64.tar.gz

sudo mkdir -p /opt/jallarhorn
sudo tar -xzf jallarhorn_0.2.3_linux_amd64.tar.gz -C /opt/jallarhorn

Then create /etc/jallarhorn/control.env (see Environment variables) and a systemd unit pointing ExecStart at /opt/jallarhorn/jallarhorn-control. The one-liner above writes these units for you.

Option C — Docker

Images are served from jallarhorn.com and pull anonymously:

docker pull jallarhorn.com/jallarhorn-control:0.2.3
# also: jallarhorn.com/jallarhorn-sensor, -farm, -license

The release archive ships a docker-compose.yml that wires the control plane to a TimescaleDB container. Extract any bundle from the Downloads table, fill in .env from the included .env.production.example, then docker compose up -d.

Option D — Windows

Two ways to run on Windows 10/11 or Server 2016+. Either way, install PostgreSQL 16 + TimescaleDB first (see Database requirement) and create the jallarhorn database.

Graphical installer (control + sensor as Windows services). A signed jallarhorn-setup-0.2.3.exe is published in the Downloads table. It installs jallarhorn-control and jallarhorn-sensor to %ProgramFiles%\Jallarhorn, registers both as auto-starting services via the bundled NSSM, generates unique secrets, writes config to %ProgramData%\Jallarhorn\jallarhorn.env and sensor.env, and opens the dashboard at http://localhost:28090. Uninstalling stops and removes both services and leaves your database untouched. Verify its signature with cosign the same way as any other artifact.

Manual ZIP + NSSM (works with the current release). Download and verify the Windows bundle, extract it, and register the services yourself:

Expand-Archive jallarhorn_0.2.3_windows_amd64.zip -DestinationPath "C:\Program Files\Jallarhorn"

# Control server + dashboard (port 28090)
nssm install JallarhornControl "C:\Program Files\Jallarhorn\jallarhorn-control.exe"
nssm set JallarhornControl AppEnvironmentExtra DATABASE_URL=postgres://postgres:PW@localhost:5432/jallarhorn JWT_SECRET=<32-hex> CONTROL_SECRET=<32-hex> HTTP_LISTEN=:28090 GIN_MODE=release
nssm start JallarhornControl

# Sensor agent
nssm install JallarhornSensor "C:\Program Files\Jallarhorn\jallarhorn-sensor.exe"
nssm set JallarhornSensor AppEnvironmentExtra SENSOR_ID=windows-1 DATABASE_URL=postgres://postgres:PW@localhost:5432/jallarhorn
nssm start JallarhornSensor

Get the signed bundle and its .sig from the Downloads table. NSSM is a free, public-domain service wrapper from nssm.cc; the graphical installer bundles it so you don't need it separately.

First-run bootstrap

On a fresh database the control service auto-creates a single default tenant, so the very first registration needs no tenant id and that first user is automatically promoted to admin:

curl -fsS -X POST http://localhost:28090/api/v2/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","email":"you@example.com","password":"CHANGE-ME"}'

After that, registration is invite-only (JALLARHORN_INVITE_ONLY=true by default). Generate further invites from Settings → Users → Invite; tokens are single-use and expire in 7 days. Set JALLARHORN_INVITE_ONLY=false to disable the gate for open self-hosted installs.

First 15 minutes — guided setup

The first time you sign in with an empty account, the dashboard shows a Set up monitoring hero instead of blank widgets. It launches a five-step wizard (also reachable any time from the Setup item in the sidebar while you have few devices) that takes you from nothing to a monitored site with alerts routed to your inbox:

  1. Name your first site. Sites are groups — one per client or location. This creates the root group everything below lands in.
  2. Find devices. Either scan a network range in CIDR form (the field is pre-filled with a guess derived from the control host's own interface — change it if your devices live elsewhere), or add a single device by hand with the same fields as Add Device.
  3. Adopt & monitor. Adopting a discovered device auto-creates the right sensors from what it answered — Ping always, HTTP and an SSL-certificate check if it serves web, SNMP basics if it replied — each with a sensible interval and a default alert rule, all assigned to your site group. Nothing is ever adopted “dead.” A manually-added device gets a Ping monitor and alert rule the same way. The Create monitors automatically checkbox (on by default) is also available on the standalone Discovery flow.
  4. Get notified. Add a notification channel. For email, guided presets (Gmail app-password, Microsoft 365, or a generic SMTP server) pre-fill the host and port and spell out exactly what to paste. Jallarhorn sends alerts through your own mail server — it never emails on your behalf — so you enter your mailbox's SMTP settings once and test them. Slack, Discord, and webhook channels take just a URL.
  5. Prove it. Fire a real test alert through the real engine and channels — respecting your severity and quiet-hours gating — and watch it arrive per channel before you rely on it. The drill alert auto-resolves itself. Test alerts are rate-limited to 3 per minute per account (a further attempt returns 429), so you can't accidentally storm your own inbox.

You can skip any step and finish later; the setup hero reappears until you actually have devices monitored.

Environment variables

VariablePurpose
DATABASE_URLPostgreSQL + TimescaleDB connection string. The control binary migrates the schema and seeds the default tenant on first run.
JWT_SECRETHMAC secret for session JWTs. 32+ hex bytes.
CONTROL_SECRETShared secret between control and sensor over /api/*.
JALLARHORN_ENCRYPTION_KEY32-byte symmetric key for at-rest credential encryption (SSH passwords, SNMP communities, etc.). Rotation procedure in the security page.
HTTP_LISTENControl HTTP listen, default 127.0.0.1:28090. Bind to 0.0.0.0 to expose.
JALLARHORN_LICENSE_URLLicense server. Defaults to https://license.jallarhorn.com.
JALLARHORN_LICENSE_MODEfail-open (default) keeps collecting if the license server is unreachable; fail-closed halts after the grace period.
JALLARHORN_INVITE_ONLYtrue (default) requires an invite token after the bootstrap admin; false opens registration.
JALLARHORN_SMTP_HOST / _PORT / _FROMSMTP relay for outbound alert email. STARTTLS expected.

Sensor variables

Set these in the sensor's environment (e.g. /etc/jallarhorn/sensor.env). The sensor pulls its check config from the control plane:

VariablePurpose
CONTROL_URLBase URL of the control plane, e.g. https://app.example.com.
CONTROL_API_KEYThe credential the sensor sends to authenticate to control. Recommended: mint a probe key in the dashboard (Settings → Probe / API keys → Create probe key) and paste it here — it is shown only once. The shared CONTROL_SECRET also works as this value.
SENSOR_NODE_IDA UUID identifying this probe (e.g. from uuidgen); control assigns it checks bound to this id in the UI. Unbound sensors run on every probe. Leave unset to run only unbound sensors. (Not to be confused with SENSOR_ID, which is any label string.)
SENSOR_ID / DATABASE_URLSingle-box mode only: point the sensor straight at the same TimescaleDB instead of a control plane.
JALLARHORN_PING_MODEprivileged (default) uses raw ICMP sockets (needs CAP_NET_RAW — the shipped systemd unit grants it); unprivileged uses UDP ping.

Sensor catalog

Jallarhorn ships 17 native sensor plugins covering the protocols a typical per-sensor tool covers, plus five generic frameworks (below) that close the long tail. Every sensor has a 60-second (1-minute) default poll, a per-sensor override, a warn threshold, an error threshold, and structured channel output for graphing.

Network probes

PluginChannels
ICMP / PingRTT, jitter, packet loss
TCP portreachability, banner read, latency
DNSA / AAAA / MX / CNAME / TXT / DNSSEC
SSL certexpiry days, chain validation, hostname mismatch
HTTPstatus, content match, latency, redirect handling

Device polling

PluginChannels
SNMP (v1/v2c/v3)get, walk, counter wrap, delta rates
SSHdisk, load, memory, uptime, CPU, custom script
WinRMCPU, memory, disk, service, process, event log, perf counter, uptime, custom WQL / PowerShell (NTLM auth)

System & services

PluginChannels
Processmatch by name + arg pattern
Diskmountpoint fan-out, % full, inode pressure
PostgreSQLreplication lag, connections, custom SQL
RedisINFO parse, key count, master/replica
HTTP-JSONJSONPath extraction (see custom sensors)
Prom-scrapePrometheus exposition (see custom sensors)
Dockerwraps any image as a one-shot probe (see custom sensors)

What's intentionally not native

Out of scope on purpose: full packet capture (use ntopng or zeek), NetFlow collection (use the dedicated nfdump pipeline, then ingest via webhook-push), Active Directory introspection (PowerShell remoting via WinRM custom_script handles this). The custom-sensor frameworks cover roughly 70% of the long tail without writing bespoke code. Need a fully-custom plugin? Contact support@jallarhorn.com — bespoke plugin integration is available for Business and Enterprise tiers.

Sensor wizard & presets

Every sensor is created through the same guided wizard — from Add sensor on any device, or the Adopt & monitor step of first-run setup. It has three modes.

Device-type presets

One click builds a working bundle of sensors for a common device type, each with a sensible poll interval and default alert rules already attached. Shared credentials (a WinRM/SSH login or an SNMP community) are entered once and applied to every check in the bundle.

PresetCreates
Web serverPing + HTTP (expects 200) + SSL certificate (warn 30 days / error 7 days to expiry)
Windows serverWinRM CPU (warn >90%, error >95%), memory, disk C:, uptime, and system event-log errors
Linux serverPing + SSH 1-minute CPU load (warn >4) + root-disk usage (warn >90%)
Network gearPing + SNMP uptime + interface in-octets (traffic)
DatabasePing + TCP port check (PostgreSQL 5432)

Single sensor + threshold builder

Pick one plugin and configure it, with inline help on the jargon fields (SNMP community, OID, WQL). Thresholds are set with a threshold builder, not two free-text boxes: each rule is a condition (above / below / equals) + value + unit + severity (warning / error / critical / info), saved as a visible alert rule you can see and edit later.

Every device detail page has an Alert rules panel per sensor — list, add, edit condition/value/severity, enable/disable, and delete rules directly, all backed by the same rules the engine evaluates.

Probe binding

A check can be pinned to a specific sensor node (probe) with the optional Run check from field — enter the sensor node's id and that node runs the check. This is how an MSP runs a client's checks from a probe sitting on the client's own network. Leave it blank to run from the default node.

Custom sensors

Five generic frameworks for anything not natively covered. All five share the same output schema, so a custom sensor's data graphs, alerts, and reports identically to a native one.

The channel format

Every custom sensor emits JSON shaped like this:

{
  "channels": [
    { "name": "cpu",    "value": 42.7, "unit": "%" },
    { "name": "memory", "value": 1.8,  "unit": "GiB" }
  ]
}

Each channel becomes a separate metric track on the device, with its own warn/error threshold and its own graph line.

exec-script

Run any local command — shell script, executable, language runtime — and parse its stdout as the channel JSON above. Per-sensor timeout, working directory, env vars, and stdin. Bounded stdout/stderr buffers. SIGKILL via process group on timeout.

http-json

GET (or POST) any HTTP endpoint, parse the response as JSON, and extract channels via JSONPath. Supports Bearer tokens, custom headers, mTLS client certs, and JSON-body POSTs. Handles missing keys and type coercion (string → float).

webhook-push

Inverts the polling model: the integration POSTs metrics to /api/v2/sensors/:id/push on its own schedule, authenticated by a SHA-256-hashed per-sensor ingest token. Useful when the source is push-only (FaaS, CI runners, scheduled jobs).

prom-scrape

Polls a Prometheus exposition endpoint (/metrics) and turns selected metrics into Jallarhorn channels. Label filtering and metric rename rules. Pair this with any existing Prometheus exporter (node_exporter, mysqld_exporter, blackbox_exporter, …) to cover the long tail.

docker

Runs docker run --rm <image> <args> on a schedule and parses stdout as channel JSON. Defaults to --network none; opt-in for network access. SIGKILL via process group on timeout. Bounded stdout/stderr. Useful for vendor-supplied CLIs, Playwright scrapers, OPC UA / Modbus / SIP / DICOM / HL7 probes via public images.

Choosing the right framework

Source shapeFramework
Local CLI or scriptexec-script
HTTP JSON endpointhttp-json
Source pushes on its own schedulewebhook-push
Prometheus expositionprom-scrape
Vendor binary / container imagedocker
A vendor-specific binary protocolcontact support — bespoke plugin integration available

Discovery

Discovery scans a network range, fingerprints whatever answers, and lets you adopt hosts into monitoring with the right sensors already attached (see first-run setup for the guided path). Adopting auto-creates sensors from the fingerprint — Ping always, HTTP + SSL if 80/443 answered, SNMP basics if the community responded — each with a default interval and alert rule, assigned to the site group you chose. The Create monitors automatically toggle (on by default) controls this.

The limits you will meet, enforced before any scanning starts:

LimitBehaviour
IPv4 onlyIPv6 ranges are rejected — a single IPv6 /64 is 1.8×1019 addresses, never enumerable.
/16 maximumAt most 65,536 addresses per scan. A broader CIDR (e.g. /8) returns 400 with "network too large — use a /16 or smaller".
One scan at a timeOnly one discovery scan runs per account. Starting a second while one is in flight returns 409 ("a discovery scan is already running"). Each scan is also hard-capped at 10 minutes.
Adopt is idempotentRe-adopting a host that is already a monitored device creates nothing — it is reported as skipped, so a double-click never produces a duplicate device or duplicate sensors.

Groups & site tree

Devices live in a tree of groups — one group per client site or location — shown in the left pane of the Devices page. Selecting a group scopes the device table on the right to that group's subtree, which gains search, filter, and sort.

Groups are also how notification routing, the per-site health tiles, and reports segment your data. Devices left ungrouped still monitor normally — they simply aren't scoped to any site (and only reach unscoped notification channels).

Maps — topology & geo

The Maps page has two tabs:

Dashboard & widgets

The dashboard is a grid of widgets you pick and arrange; your choice and layout are saved in the browser. Add or remove any of these from the widget picker:

WidgetShows
Status SummaryOverall up / warning / down counts
Live Alert FeedOpen alerts as they fire
Sensor ChartA metric chart, titled by the sensor's name
Device StatusPer-device health table
Per-Site HealthOne tile per group, coloured by rollup — the MSP's morning glance
GaugeA single-value dial for one channel
Top-N SensorsA compact ranking (see Reports)

The separate /monitor route is a viewport-sized status board for wall-mounted displays.

Uptime & status history

Every device detail page shows uptime and a status-history strip, both derived honestly from the presence of metric samples: a poll that ran and stored a sample counts as up; a sample that should have arrived but didn't counts as down. There is no separate "availability" bookkeeping to drift out of sync.

That last distinction is deliberate: a brand-new or never-polled sensor reads "No data" (waiting for its first check), which is different from a sensor whose checks are failing. An erroring sensor gets its own status and a reason message, plus a link to its last alert.

Reports — SLA & Top-N

Two report types, each exportable as PDF, CSV, or HTML from the Reports page or the API. Both are built on the same metric-sample-presence idea as the device uptime model — a present sample is up, a missing one is down.

ReportContents
SLAUptime % per device or group over a period, with the underlying downtime intervals. GET /api/v2/reports/sla?device_id=…&from=…&to=…&format=pdf|csv|html
Top-NRank sensors by downtime, average latency, or alerts over a time range (default top 10, up to 100). GET /api/v2/reports/topn?metric=downtime|latency|alerts&n=…&format=pdf|csv|html

Add /preview to either path (e.g. /reports/sla/preview) to get the same data as JSON for your own rendering.

Alert rules

An alert rule has a sensor, a comparison operator (>, <, ==, etc.), and two thresholds: warn and error. The alert engine runs every 30 seconds; when a sample crosses a threshold, an alert opens with that severity. Alerts auto-resolve when the underlying value crosses back. Duplicate suppression keeps a flapping sensor from generating storms.

Availability alerting is on by default — no rule required. A device that goes down produces no reading at all, so a numeric threshold (e.g. "latency above 1000 ms") can never trip on it. Every sensor therefore also has built-in availability alerting: when a sensor's status becomes down (unreachable / no response), the engine opens an error-severity alert — "Device X / sensor Y is down (no response)" — and auto-resolves it the moment the sensor answers again. This applies to every sensor created any way (setup wizard, adopt, discovery, or by hand), respects dependency and maintenance suppression and channel gating like any alert, and never double-fires with a latency rule. To opt a sensor out of availability alerting, pause it.

Operators can acknowledge an alert (silences further notifications without closing it) or resolve it (closes immediately, even if the underlying value is still over threshold). Auto-resolve fires when the sensor returns to OK for the configured cooldown period. Every notification (webhook, Slack, Teams, Discord, email, push) names the affected device and sensor; the outbound webhook payload additionally carries the device address, sensor status, severity, alert id, and timestamp.

Dependency suppression

Mark a sensor as depending on another (e.g. an HTTP check on a web app depends on the ICMP check of its host). When the parent goes red, child alerts are recorded with status: suppressed — visible in the audit log but no notifications are sent. When the parent recovers, child alerts that are still real fire normally.

Set this with the Depends on picker on each sensor row of the device detail page — choose the sensor this one depends on, and the engine handles the suppression from there.

Maintenance windows

Schedule planned downtime per device or per sensor. While the window is active, alerts are auto-suppressed; they still appear in the audit log labeled status: maintenance. Recurring windows are supported via cron-style expressions.

Notification channels

ChannelTransport / Notes
EmailSends through your own mail server over SMTP with STARTTLS — Jallarhorn never emails on your behalf. Per-channel guided presets (Gmail app-password, Microsoft 365, generic SMTP) pre-fill host/port and spell out what to paste, with a test-send to validate. A global JALLARHORN_SMTP_* default is also available.
SlackIncoming webhook. Severity-coloured blocks with sensor, device, current value, and deep-link to the alert.
Microsoft TeamsAdaptive Card 1.4 via incoming webhook. Same payload shape as Slack.
DiscordIncoming webhook. Useful for community-run installs.
PagerDutyEvents API v2. Maps Jallarhorn severities to PagerDuty's warning / error / critical.
OpsgenieAlert API. Auto-resolve when the Jallarhorn alert clears (closes the Opsgenie incident).
Web Push (VAPID)Browser + PWA push notifications with min-severity, quiet-hours, and timezone awareness.
Generic webhookOutbound JSON POST to any URL. Configurable headers, retry-with-backoff, HMAC-SHA-256 request signing.

Every channel has a Send test button in the dashboard. The test payload identifies itself as a test so it can't be mistaken for a real incident.

Notification routing

By default every alert goes to every channel — zero-config and safe. To send a client's alerts only to that client's queue, give a channel a site scope (one or more groups) and a minimum severity. The channel then receives an alert only when both are true: the alert's device sits in one of the scoped groups' subtrees, and the alert meets the severity floor.

An unscoped channel keeps receiving everything (the pre-existing behaviour). A device with no group only reaches unscoped channels — an unfiled device's alert is never silently routed to a client-specific queue.

Quiet hours & timezone

Each channel can set a quiet-hours window (start / end in HH:MM) and a timezone. An alert that arrives inside the window is suppressed and logged as such with the reason (see the notification log). Windows may span midnight (e.g. 22:0007:00).

Escalations bypass quiet hours. An escalation step fires even inside a quiet window — the whole point of an escalation is that an unacknowledged problem must eventually break through. Escalation pages still respect the channel's minimum severity; only the quiet-hours gate is bypassed, and any send that is still suppressed (below severity) is recorded in the log.

Escalation chains

An escalation policy is an ordered list of steps: after N minutes of an alert staying unacknowledged, notify a chosen set of channels; then the next step after its own delay; optionally repeat the final step on a cadence. For example: "Slack at T+0, page on-call at T+5 min if not acknowledged, page secondary at T+15 min."

Notification log

The bell in the top bar and the Notification log page show every send the engine attempted, so a bad SMTP password can't fail silently. Each row is one of:

StatusMeaning
DeliveredThe channel accepted the notification.
FailedDelivery errored (e.g. wrong SMTP credentials) — the error message is shown. The bell badges unseen failures.
SuppressedIntentionally not sent, with the reason: quiet hours, below the channel's minimum severity, or dependency/maintenance suppression. Suppressed is not a failure.

Filter the log by channel, status, and time period.

Federation with jallarhorn-farm

Run multiple Jallarhorn control instances (one per site, one per tenant, one per regulatory boundary) and view them through a single aggregated UI. Each control keeps its own database; the farm executable is a stateless aggregator that fans queries out, merges results, and serves a unified dashboard at port 28092.

Seeding the farm

Start jallarhorn-farm with --seeds host1:28090,host2:28090,host3:28090. The farm GETs /api/v2/farm/announce on each seed, learns its peers, and continues discovering via SWIM-style gossip on UDP. New control instances joining the farm auto-announce.

Aggregated endpoints

EndpointBehaviour
GET /api/v2/farm/statsSum of stats across instances
GET /api/v2/farm/devicesUnion of devices with origin annotation
GET /api/v2/farm/alertsUnion of open alerts
GET /api/v2/farm/sensorsUnion of sensors

Partial failures (one control unreachable) return the merged result with X-Partial-Results: true and the failing instance(s) listed in the response body. Per-instance timeout 5s.

Write-proxy via ?instance=

Writes (create device, ack alert, edit sensor) need to land on the specific control that owns the record. Add ?instance=<id> to the request — the farm proxies it. Without that parameter, writes return 400 rather than silently writing to a random instance.

REST API

Jallarhorn-control exposes a REST API at /api/v2/* (JWT-authenticated, for humans and integrations) and a simpler shared-secret surface at /api/* (used by the sensor binary to publish metrics). All responses are JSON. Per-IP rate limit on /api/v2/auth/login: 10/min then exponential backoff.

Authentication

# Login
curl -sS https://app.example.com/api/v2/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"alice","password":"..."}'
# → { "access_token": "eyJ...", "refresh_token": "eyJ...",
#     "expires_in": 900, "token_type": "Bearer" }

# Subsequent calls — send the access_token as a Bearer token
curl -sS https://app.example.com/api/v2/devices \
  -H 'Authorization: Bearer eyJ...'

JWTs land in an httpOnly Strict cookie when issued via the web flow. Programmatic clients should use the Authorization: Bearer header.

Resources

PathOperations
/api/v2/usersList, create, update, delete (admin only)
/api/v2/devicesList, create, get, update, delete; tagged + grouped
/api/v2/devices/:id/sensorsList + create sensors on a device (create = POST); see Creating a sensor
/api/v2/sensors/:sensorIdGet, update, delete a sensor; set dependency + probe node
/api/v2/groupsCRUD device groups; /tree returns the hierarchy with rollup status per node
/api/v2/metrics/:sensorIDTime-series query — ?from=…&to=…&channel=…&agg=avg|max|min|count
/api/v2/devices/:id/uptimePer-sensor + device-rollup uptime for 24h and 30d
/api/v2/sensors/:id/historyStatus-history timeline — ?window=24h|7d
/api/v2/alertsList, ack, resolve; filter by severity / device / sensor
/api/v2/alert-rulesCRUD on threshold rules + dependency suppression
/api/v2/discovery/scanKick off a CIDR scan; poll /jobs/:id and adopt with /adopt
/api/v2/reports/slaSLA report (?format=pdf|csv|html); /sla/preview returns JSON
/api/v2/reports/topnTop-N by downtime / latency / alerts (?format=pdf|csv|html); /topn/preview returns JSON
/api/v2/notificationsConfigure channels (site-scope + min-severity), send tests, view delivery log
/api/v2/escalation-policiesCRUD escalation chains (ordered steps, repeat-last)
/api/v2/webhooksOutbound webhook subscriptions with HMAC signing
/api/v2/maintenanceSchedule + manage windows
/api/v2/statusComponent health rollup; GET /api/v2/health for liveness
/api/v2/wsWebSocket subscription for real-time metric + alert pushes
/api/v2/auditAudit log query

Creating a sensor

Sensors are created on a device: POST /api/v2/devices/:id/sensors. The body is { name, sensor_type, interval, config }. The check runs against the device's address; config carries the per-plugin parameters in the canonical shape:

{
  "target": "10.0.0.5",        // optional — overrides the device address
  "params": { /* plugin-specific key/values (strings) */ }
}

Put every plugin parameter under params. (Credential params — password, community, auth_pass, priv_pass, secret — are encrypted at rest.) One example per common plugin:

# TCP port check
curl -sS -X POST https://app.example.com/api/v2/devices/$DEV/sensors \
  -H 'Authorization: Bearer eyJ...' -H 'Content-Type: application/json' \
  -d '{"name":"https-port","sensor_type":"tcp","interval":"60 seconds",
       "config":{"params":{"port":"443","timeout_ms":"2000"}}}'

# HTTP with content match
#   config.params: { "url", "method", "expected_status", "content_match" }
#   {"params":{"url":"https://example.com/health","expected_status":"200","content_match":"OK"}}

# SNMP OID poll
#   config.params: { "community", "oid", "version" }
#   {"params":{"community":"public","oid":"1.3.6.1.2.1.1.3.0","version":"v2c"}}

# SSL certificate expiry
#   config.params: { "hostname", "warn_days", "err_days" }
#   {"params":{"hostname":"example.com","warn_days":"30","err_days":"7"}}

# DNS record
#   config.params: { "record_type", "expected_value" }
#   {"params":{"record_type":"A","expected_value":"1.2.3.4"}}

# SSH command (returns a single number)
#   config.params: { "username", "password", "command" }
#   {"params":{"username":"root","password":"...","command":"uptime | ..."}}

# ICMP ping — no params
#   {"params":{}}

WebSocket

Connect with the same Bearer token (sent as a ?token= query param). Server pushes { "type": "metric", … } or { "type": "alert", … } messages per tenant; the dashboard uses this for live tiles.

Node / TypeScript SDK

npm install @jallarhorn/client

import { Jallarhorn } from "@jallarhorn/client";
const j = new Jallarhorn({
  baseURL: "https://app.example.com",
  token: process.env.JALLARHORN_TOKEN!,
});

const devices = await j.devices.list();
await j.alerts.ack(alertId);

Python SDK

pip install jallarhorn

from jallarhorn import Jallarhorn
j = Jallarhorn(base_url="https://app.example.com",
               token=os.environ["JALLARHORN_TOKEN"])

for d in j.devices.list():
    print(d.name, d.status)

j.alerts.ack(alert_id)

Both SDKs share the same method names + paginated-iterator shape, so a script translated between languages reads near-identically. A /api/v3/* migration — if it ever happens — would ship as @jallarhorn/client@2.x / jallarhorn-client>=2.0.

Mobile / PWA

The Jallarhorn dashboard is a Progressive Web App — installable to the home screen on iOS and Android, with offline-aware loading for cached views. There is no separate native app; the PWA covers the use cases that warrant one (alerts, ack, drill-down). The /monitor route renders a viewport-sized dashboard with safe-area awareness for the iPhone notch — useful as a status-board page on a wall-mounted tablet or as the on-call engineer's lock-screen shortcut.

Web Push

VAPID-authenticated push notifications. Users opt in from Settings → Notifications → Browser push. Per-user preferences: minimum severity, quiet hours, timezone. Operators can send a test push from the same screen.

Web Push is per-person, not one of the group-routed channels. Each user subscribes their own browser or installed PWA and sets their own severity floor and quiet hours; a push follows that individual's preferences rather than a channel's site-scope routing policy.

Migrate from per-sensor monitoring

A staged migration where Jallarhorn and the legacy tool run side-by-side; sensors move group-by-group; the legacy install gets retired only after the new one is proven.

Phase 1 — Stand Jallarhorn up alongside

Install per Install & configure. Point Jallarhorn at the same network segments your existing tool already monitors. Don’t disable any of the legacy sensors yet.

Phase 2 — Auto-discover

Run POST /api/v2/discovery/scan against each CIDR. The discovery engine sweeps ICMP, walks SNMP sysDescr / sysName / sysOID, reverse-DNS-looks-up everything, and produces a candidate list. Adopt with one click per device — Jallarhorn then auto-creates sensors based on the device type fingerprint.

Phase 3 — Sensor mapping

Most legacy per-sensor types map 1:1 to a Jallarhorn native plugin (ping, SNMP, HTTP, SSL, SSH, WinRM, DNS, TCP, disk, process, PostgreSQL, Redis, Docker). For the long tail, a custom-sensor framework usually covers it.

Phase 4 — Alert rules

Recreate threshold rules in Jallarhorn (UI or API). Run both alert engines in parallel for a week, watching for divergence. The Jallarhorn alert log includes a source_sensor field so cross-system comparison is mechanical.

Phase 5 — Cutover

Disable the legacy sensors group-by-group as confidence builds. Keep the old install read-only for ~30 days as a rollback option, then decommission. Save the legacy Probe Devices XML export — Jallarhorn can ingest it into a tagged group called imported-legacy.

For a side-by-side feature + pricing comparison, see how Jallarhorn compares.

Docs version: 2026-07-06. Product version: see /health on your control instance. Something missing or wrong? Email support@jallarhorn.com.