PYLONMON

Documentation

Everything you need to go from zero to paged-when-it-matters.

Quickstart

Create a free account, add your first monitor, and start getting alerts in about two minutes.

# at the end of your backup / cron job:
curl -fsS https://pylonmon.com/hb/your-mon-id

If we don't hear from it within the window, you get paged. The silence is the signal.

Monitor types

Beacon — the whole machine reports in

Watch an entire box, not just one endpoint. Drop our open-source agent on a server, Pi, or laptop and it pushes that machine's vitals to us — CPU, memory, per-mount disk, temperature, load, uptime, plus anything you add. Because it pushes outbound, there are no open ports and it works behind NAT, CGNAT, and firewalls. If the box goes quiet you get paged — the silence is the signal. Install steps and metric rules under pylon-beacon.

Heartbeat — it pings us

Best for cron jobs, backups, and scripts. Your job hits its unique URL when it finishes; miss the window and we alert. No inbound access to your network required.

HTTP/S — we poll it

Point Watch at a URL and we check it on your schedule from off-site. Any non-2xx response, or an unreachable host, trips an alert with the exact status.

TCP — we connect to a port

Give us host:port and we open a connection on your interval — great for databases, SSH, game servers, or anything that speaks TCP but not HTTP.

Ping — we ping a host

Give us a hostname or IP and we send an ICMP echo (a "ping") on your interval — the simplest reachability check for routers, printers, NAS boxes, and IoT devices that don't expose a port.

DNS — we resolve a name (paid)

Give us a hostname and we resolve it on your interval; optionally assert it resolves to an expected IP. Catches DNS outages and hijacks before your users do.

UDP — we probe a port (paid)

Give us host:port and we send a UDP probe — for DNS servers, game servers, VPNs, and other services that speak UDP.

Prometheus — a metric threshold (paid)

Alert when a number crosses a line. Point us at a /metrics or Prometheus query URL and we pull it, or push values to us from anywhere — full details and examples under Prometheus.

Automatic extras on paid plans

Heartbeats in cron

Append the ping to the job so it only fires on success:

# run at 2am; only pings Watch if the backup command succeeded
0 2 * * *  /usr/local/bin/backup.sh && curl -fsS https://pylonmon.com/hb/your-mon-id

HTTP/S monitors & headers

For endpoints behind auth, add request headers to an HTTP monitor (one per line) — for example an API key or bearer token. They're stored with your monitor and sent on every poll, and masked in the UI.

Authorization: Bearer xxxxxxxx
X-Api-Key: yyyyyyyy

Alert channels

Alerts go to channels. Every account starts with your email channel, so alerts work out of the box. Add more on the Alerts tab:

Each monitor picks its own channels (as many as you like — that's free). A monitor with no channels selected sends no alerts — it stays silent until you choose where it should page, so an auto-created monitor never surprises you by blasting every channel at once.

Webhook-family channels (custom webhook, Slack, Discord, …) can carry a custom JSON payload template on paid plans — override the built-in body entirely with your own shape and placeholders:

{"text": "{{status}}: {{name}} — {{detail}}", "target": "{{target}}", "link": "{{url}}"}

Placeholders: {{title}}, {{status}} (up/down), {{name}}, {{target}}, {{detail}}, {{url}}. The rendered result must be valid JSON (we validate it when you save).

Escalation (paid)

Every plan includes escalation: if a monitor is still down after a delay you set, PylonMon also alerts another set of channels. Stack rungs to build an on-call ladder — nudge a backup channel after 15 minutes, page the on-call after an hour, set a rung to repeat until recovery. Acknowledging the incident silences the ladder while you work.

Incidents (paid)

On paid plans every outage automatically opens an incident — a record you can acknowledge, annotate, and close with a resolution write-up.

On-call rotations (Team+)

Stop hardcoding people into your alerting. On Team and Enterprise, the workspace owner flips on-call on under Settings → On-call rotation; the rotation itself is then managed from its own On-call section on the Alerts tab:

Public status pages (paid)

Give customers a live status page that shows only the monitors you choose. Turn it on under Settings → Admin → Status page & API (it mints a token you can rotate any time), then flip "show on my public status page" on each monitor you want visible. The page lives at:

https://pylonmon.com/status/your-status-token

Monitors tagged on an open SEV1–SEV3 incident show as degraded (amber); SEV4 incidents never appear. The status token is separate from every other URL, so sharing the page exposes nothing but status.

Maintenance windows (paid)

Planned downtime shouldn't page you. Set a daily maintenance window (UTC) on a monitor from its edit form — we keep checking and recording history, but suppress alerts during the window so expected restarts and backups stay quiet.

Logs & retention

Every check result, alert, incident, and account action is logged — filter and export it as CSV from the Logs tab. We keep history by a mix of age and volume: your tier's number of days or its event count, whichever limit you hit first, so one noisy monitor can't evict the rest of your history.

Exposure — what the internet can see

Monitors answer “is it up?”. Exposure answers “what is it showing?” — and tells you when that changes. Add a domain in the Exposure tab and we watch its public surface for you. It reads only public information, so there is nothing to install, nothing that touches your servers, and no port scanning.

Every asset gets the same six checks, re-run three times a day (or on demand — hit on the asset). Every check reports a row every time, even when it is clean or could not run, so you always know what was looked at:

Illustration — one asset's page after a scan
shop.example.com reputation ok TLS ok DNS ok mail auth weak
✓ Ownership verified — port scanning is on for this asset.
Certificate valid — 38 days left
SPF is not enforcing
tcp/3389 (Remote Desktop) newly open This is expected
Open ports — 1 newly open
Alert to  #incidents ✓ Email — ops@ ✓ SMS

Verifying ownership (required for port scanning)

The first four checks read public records, so they run on any domain you add. Port scanning is different: it sends traffic to the host. We will not do that on somebody's say-so, so we ask you to prove you control the domain's DNS first. Open the asset in the Exposure tab and add the TXT record it shows you:

Then press Verify now. Once it passes, port scanning turns on for that asset and runs with the other checks. We probe a fixed, bounded list of about 35 common ports (remote access, databases, mail, admin panels) — not an exhaustive sweep — and only tell you when something opens or closes. Removing the TXT record does not retroactively un-verify, but deleting the asset stops everything.

The easiest proof — run one command on the machine:

curl https://pylonmon.com/verify/pylonmon-verify=YOUR-TOKEN

We compare the source address of that request against the asset. Nothing to serve, no DNS to edit, and it works from a box whose inbound ports are all firewalled — outbound is enough. If the machine sits behind NAT, this proves its public egress address, which is exactly the address the internet (and our scanner) sees.

Watching an IP address? Use the command above, or serve a file containing the token at http://your.ip.address/.well-known/pylonmon-challenge. An IP cannot be verified by DNS — its reverse record belongs to your hosting provider, not to you.

Ports you mean to have open

A web server is supposed to answer on 80, 443 and 22 — that is not news. So the first scan sets the baseline: whatever is open then becomes this host's expected normal, and you are only told when something opens or closes afterwards.

The exception is a short list of ports that should essentially never face the internet — Remote Desktop, SMB, and bare database ports like MySQL, PostgreSQL, Redis, MongoDB and Elasticsearch. Those are reported even at the baseline, because "it was already like that" is not a reason to stay quiet, and they are never adopted automatically.

Anything you do mean to expose can be marked expected — either from the finding itself (This is expected) or by editing the Expected open list on the asset. Expected ports are still shown, so the list stays honest about what is exposed; they just never alert.

Getting told about findings

Each asset has its own Alert to row — pick any of your existing channels (email, Discord, Slack, SMS…), exactly like a monitor. A new asset starts with nothing selected: findings show up on the dashboard and in the asset's page, but nothing is sent until you pick a channel — and the page says so out loud rather than letting you assume otherwise.

Alerts fire on change, not on state: you are told when a finding appears and again when it clears. A problem that is still there tomorrow does not page you again — one open port does not become three notifications a day. Every scan is also written to your Logs tab, so the full history is searchable even for findings that never paged.

What can be watched: public domains, hostnames, and public IP addresses. Private addresses (192.168.x, 10.x) and internal-only names (.local, .home.arpa) are refused — the public internet cannot see them, so neither can we. Watch those with a normal uptime monitor plus pylon-beacon instead.

API & API keys (paid)

Paid workspaces can drive everything from code — monitors, alert channels, and incidents — with a workspace API key. Manage keys under Settings → Admin → Status page & API (up to three named keys; rotate or delete any of them instantly). A key identifies your whole workspace, so treat it like a password. Keys come in three scopes: full workspace access; read-only — strictly the list/GET endpoints, no writes anywhere (perfect for wallboards, dashboards, and scrapers); and (on Enterprise) the agent scope — read plus incident write-back only, built for BYO-AI agents.

Authentication

Send the key on every request in a header — either works, and it always travels over HTTPS, never in the URL:

Authorization: Bearer YOUR_KEY
# or
X-Api-Key: YOUR_KEY

The base URL is https://pylonmon.com. Requests and responses are JSON. Unauthenticated calls get 401; a key on a workspace that has dropped to the free plan is inert.

Monitors

# list your monitors
curl -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/checks

# create one (type: heartbeat | http | tcp | ping | dns | udp | prometheus)
curl -H "Authorization: Bearer YOUR_KEY" \
     -d '{"name":"API","type":"http","url":"https://api.example.com/health","period":60,"tags":["prod","payments"]}' \
     https://pylonmon.com/api/checks

# update (name, period, url, channels, tags, escalations, slow_ms, slow_channels, public...)
# PARTIAL: fields you omit are left alone; send "channels":[] to actually clear them
curl -H "Authorization: Bearer YOUR_KEY" \
     -d '{"name":"API (prod)","period":30,"tags":["prod","payments"]}' \
     https://pylonmon.com/api/checks/MONITOR_ID/update

# pause / resume (toggles), then delete
curl -X POST   -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/checks/MONITOR_ID/pause
curl -X DELETE -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/checks/MONITOR_ID

The create call returns the new monitor including its id. A heartbeat monitor's id is also the token in its /hb/<id> check-in URL — and those check-ins need no key, the unique URL is the credential.

Tags (tags, array of strings — labels still accepted) group monitors for search and filtering — normalized to lowercase, spaces become dashes, up to 8 per monitor. Slow routing (slow_channels, array of channel ids) sends slow-response alerts and their recoveries to a different channel set than down alerts; empty means "same channels as down". Both come back on every monitor in GET /api/checks, so your scripts can filter on them.

Alert channels

# list channels
curl -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/channels

# create — kind: email | sms | whatsapp | voice | discord | slack | telegram
#          | ntfy | webhook | googlechat | teams | webex | zapier
# email/sms/whatsapp/voice use "addr"; the rest use "url" (telegram: url=token, addr=chat id)
curl -H "Authorization: Bearer YOUR_KEY" \
     -d '{"kind":"slack","name":"#ops","url":"https://hooks.slack.com/services/XXX"}' \
     https://pylonmon.com/api/channels

# update, then delete
curl -H "Authorization: Bearer YOUR_KEY" -d '{"url":"https://hooks.slack.com/services/YYY"}' \
     https://pylonmon.com/api/channels/CHANNEL_ID/update
curl -X DELETE -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/channels/CHANNEL_ID

Attach channels to a monitor by passing their ids in the monitor's channels array (or slow_channels for a separate slow-alert route) on create or update. Tier limits and paid-channel gates (SMS, voice, custom payloads…) apply exactly as they do in the UI.

The API sees your workspace's shared channels only. Members' private on-call channels are personal: they're created and managed in the portal (Settings → On-call), never appear in API listings, and can't be attached to monitors — by design, so automation can't page someone's private phone.

Incidents

# list open + recently resolved incidents
curl -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/incidents

# acknowledge (silences escalation paging), annotate, resolve with a write-up
curl -X POST -H "Authorization: Bearer YOUR_KEY" https://pylonmon.com/api/incidents/INCIDENT_ID/ack
curl -X POST -H "Authorization: Bearer YOUR_KEY" -d '{"text":"failing over to replica"}' \
     https://pylonmon.com/api/incidents/INCIDENT_ID/note
curl -X POST -H "Authorization: Bearer YOUR_KEY" -d '{"note":"bad deploy — rolled back"}' \
     https://pylonmon.com/api/incidents/INCIDENT_ID/resolve

# declare an incident from your own tooling (deploy scripts, chatops...)
curl -X POST -H "Authorization: Bearer YOUR_KEY" \
     -d '{"title":"checkout errors","detail":"elevated 500s","checks":["MONITOR_ID"],"status_impact":true}' \
     https://pylonmon.com/api/incidents/declare

Wire these into PagerDuty-style tooling, deploy pipelines, or a chatbot — anything that should open, work, or close incidents without a browser.

pylon-beacon — the node agent (free — 3 nodes; more on paid)

One binary. No open ports. Full node vitals. pylon-beacon is our open-source (MIT) monitoring agent: a single static binary that pushes your machine's vitals to PylonMon — CPU, memory, per-mount disk, temperature, load, uptime, plus anything you add. Because it pushes outbound, it works behind NAT, CGNAT, firewalls, and on boxes you'd never open a port on. No Prometheus, no scrape configs, no sidecar stack — and if the box goes silent, you get paged. The silence is the signal.

1 · Install

Linux (writes /usr/local/bin/pylon-beacon, a config at /etc/pylon-beacon.conf, and a systemd unit):

curl -fsSL https://pylonmon.com/beacon.sh | sh

Windows (PowerShell, as admin — installs to Program Files\pylon-beacon and registers a service):

irm https://pylonmon.com/beacon.ps1 | iex

Both installers prompt for your API key, or read it from PYLON_KEY if it's already set. Prefer building from source? go build in the repo — it's a single main.go, stdlib only.

2 · The key

Create an ingest-scoped API key (Settings → Admin → Status page & API → + New key → scope "ingest"). Ingest keys can push check-ins and metrics and do nothing else — they can't read your monitors, touch incidents, or change anything, so they're safe to sit on every box you own. One key can serve all your nodes. Ingest keys are free (the rest of the API is paid), so anyone can run the agent — the free plan covers 3 nodes, and Pro raises that to your full monitor cap.

3 · The config file

# /etc/pylon-beacon.conf   (Windows: Program Files\pylon-beacon\beacon.conf)
# The only REQUIRED line is the key — everything below it is optional.
key      = pm_xxxxxxxxxxxx       # your ingest-scoped API key
url      = https://pylonmon.com  # or your own instance
node     = db01                  # monitor name — defaults to the hostname
interval = 20                    # seconds between pushes (default 20, min 15)

[custom]     # extend what it collects: name = command
# each command runs every push; the FIRST NUMBER in its output is the metric
gpu_temp_c    = nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader
queue_depth   = redis-cli llen jobs
raid_degraded = sh -c "grep -c _ /proc/mdstat"
# is the local site up? 1 = yes, 0 = no — then set a vital rule: site_up < 1
site_up       = sh -c "curl -sf http://localhost/ >/dev/null && echo 1 || echo 0"

[probes]     # check LAN targets from the inside: name = kind target [timeout-seconds]
pihole   = http http://192.168.1.240/admin   # up = any response below 500 (self-signed certs OK)
nas_smb  = tcp 192.168.1.50:445              # up = the port accepts a connection
gateway  = ping 192.168.1.1                  # system ping tool, one echo — no privileges needed
slow_box = http https://10.0.0.9:8443 10     # per-probe timeout (default 5s)

[snmp]       # network gear: switches, APs, UPSes, printers — v2c, read-only GETs
target    = 192.168.1.1          # host or host:port (default 161)
community = public
timeout   = 3                    # seconds per poll
# every other key is a metric name = the OID to read
# snmp_up (1/0) is ALWAYS reported — alert on snmp_up < 1 to catch a dead device
uptime_ticks  = .1.3.6.1.2.1.1.3.0          # sysUpTime
wan_in_octets = .1.3.6.1.2.1.2.2.1.10.1     # ifInOctets on interface 1
wan_oper      = .1.3.6.1.2.1.2.2.1.8.1      # ifOperStatus: 1 = up

[logwatch]   # count pattern matches in a growing log: name = file | regex | window-seconds
nginx_5xx_5m  = /var/log/nginx/access.log | " 5\d\d " | 300
tracebacks_5m = /var/log/app/app.log | Traceback | 300

[logs]       # ship log lines to PylonMon's log search: name = file-or-glob | service (optional)
app = /var/log/app/*.log | my-app
k8s = /var/log/containers/*.log             # k8s/containerd: services named per container

[proxmox]    # on a Proxmox host: guests, storage and quorum ride along with the vitals
enabled = true

Built-in collectors (no config needed): cpu_pct · mem_pct · disk_pct per mount · load1 · temp_c (Linux) · uptime_s. Custom entries ride along in the same push.

Running it on a web server? Two complementary options. For public up/down, add a normal PylonMon HTTP monitor — checking from off-site is actually better, because it sees what your visitors see. For a local check the outside world can't do — "is nginx serving on localhost even though the public route is broken?", or an internal-only service — use the site_up recipe above and set a vital rule site_up < 1. Same beacon, one line.

4 · Watch the LAN from the inside: [probes]

Our pollers live on the public internet — which is why they can never reach the NAS at 192.168.x, the hypervisor's UI, or anything else that rightly refuses to face the internet. The beacon already lives inside, so [probes] has it check those targets from where it stands: http (up when the response is below 500 — an auth prompt is a living service; self-signed certs accepted), tcp (connect), or ping (the system ping tool, no special privileges). Each probe becomes its own monitor under the node — real measured latency in the graphs, incidents, alerts to the node's channels — and nothing is exposed: results leave over the same outbound push as the vitals, no listening socket, no tunnel. Up to 20 per agent; a probe that times out reports down, and if the agent itself goes quiet the node's silence page covers everything rather than paging once per probe.

The agent also speaks SNMP (switches, APs, printers, UPSes), watches log files for patterns, and reports Proxmox guests and quorum — see the README for [snmp], [logwatch] and [proxmox] — and with [logs] it ships the log lines themselves into log search & incident correlation, docker/k8s formats unwrapped automatically.

5 · What happens in PylonMon

Under the hood it's one endpoint — POST /api/ingest/exporter with {"node":"db01","interval":60,"metrics":{…}} — so anything that can speak JSON over HTTPS can be a beacon: the agent is a convenience, not a requirement.

Bring-your-own-AI agents (Enterprise)

We don't sell you an AI SRE — we page yours. If you run an agent with access to your own infrastructure (Claude, or any agent runtime you trust), PylonMon can page it like a teammate: the alert arrives with full context, the agent investigates on your systems with your credentials, and it writes findings back onto the incident. PylonMon never holds your infrastructure credentials, never runs a model, and never touches your instances — you stay in control of what the agent can reach.

1 · The agent alert channel

Add a channel of type AI agent (Alerts → + → AI agent) pointing at your agent's HTTPS endpoint. Every alert and recovery POSTs a structured JSON envelope — no prose parsing:

{
  "source": "pylonmon", "version": 1,
  "event": "alert",                     // or "recovery"
  "title": "api-prod is DOWN",
  "fields": { "Status": "connection refused", "Type": "http" },
  "monitor": { "id": "…", "name": "api-prod", "type": "http", "url": "…",
               "status": "down", "detail": "…", "tags": ["prod","payments"] },
  "incident": { "id": "…", "severity": 3, "started": 1752969600, "acked": false },
  "actions":  { "ack": "https://pylonmon.com/api/incidents/…/ack",
                "note": "…/note", "resolve": "…/resolve" },
  "api": { "mcp": "https://pylonmon.com/mcp", "auth": "Authorization: Bearer <key>" },
  "instructions": "You are the on-call agent… ack first, note evidence, resolve only when verified."
}

The envelope carries no credentials — your agent authenticates its write-backs with its own key:

# the write-back loop, from your agent
curl -X POST -H "Authorization: Bearer AGENT_KEY" https://pylonmon.com/api/incidents/ID/ack
curl -X POST -H "Authorization: Bearer AGENT_KEY" \
     -d '{"text":"db01 disk 98% full — journal logs; vacuuming now (evidence: df -h output)"}' \
     https://pylonmon.com/api/incidents/ID/note
curl -X POST -H "Authorization: Bearer AGENT_KEY" \
     -d '{"note":"rotated journald, added retention cap; disk at 41%, monitor green"}' \
     https://pylonmon.com/api/incidents/ID/resolve

Notes land on the incident timeline attributed as <key name> — whoever gets paged sees the agent's investigation before they've rubbed their eyes.

2 · Agent-scoped API keys — the leash

Create the agent's key with the agent scope (Settings → Admin → Status page & API): it can read monitors, incidents and logs, and ack / note / resolve incidents — and that is all. It cannot create, edit, pause, or delete monitors or channels, declare incidents, publish to your status page, or read teammates' data. A misbehaving agent can annotate your incidents; it can never dismantle your monitoring.

3 · The MCP endpoint

If your agent runtime speaks MCP (Model Context Protocol), skip the glue code entirely — PylonMon is a remote MCP server at https://pylonmon.com/mcp (Streamable HTTP, authenticate with your API key):

// e.g. a Claude-style MCP config
{ "mcpServers": { "pylonmon": {
    "url": "https://pylonmon.com/mcp",
    "headers": { "Authorization": "Bearer AGENT_KEY" } } } }

Tools exposed (10): list_monitors · get_monitor · list_incidents · get_incident · ack_incident · add_incident_note · resolve_incident · search_logs · list_runbooks · get_runbook. The intended flow is baked into the tool descriptions: ack first, read any runbook attached to the monitor, investigate, note evidence, resolve only when verified — so any competent agent behaves like a disciplined on-call engineer out of the box.

4 · Copy-paste agent skill (Claude Code & friends)

Fastest start: save the snippet below as ~/.claude/skills/pylonmon/SKILL.md (Claude Code), or paste it into any agent runtime's system prompt. Then drop your agent-scoped key in ~/.pylonmon/key — outside any git repo, so it never lands in a commit or a transcript — and tell your agent "check my alerts".

---
name: pylonmon
description: Act as this workspace's on-call agent for PylonMon —
  read monitors/incidents/logs over MCP, ack/note/resolve incidents.
  Use when asked to check alerts or investigate an incident.
---

# PylonMon on-call agent

MCP endpoint: POST https://pylonmon.com/mcp (JSON-RPC 2.0).
Auth: read the key from ~/.pylonmon/key — NEVER print it, never
commit it. If the file is missing, ask a human to create an
AGENT-scoped key (portal: Settings → Admin → Status page & API)
and save it there.

    PMKEY=$(cat ~/.pylonmon/key)

Tools (call via method "tools/call", params {name, arguments}):
list_monitors · get_monitor{id} · list_incidents · get_incident{id}
· ack_incident{id} · add_incident_note{id,text}
· resolve_incident{id,note} · search_logs{q,limit}
· list_runbooks · get_runbook{id}

Example — what is on fire:

    curl -s -X POST https://pylonmon.com/mcp \
      -H "Authorization: Bearer $PMKEY" \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
           "params":{"name":"list_incidents","arguments":{}}}'

Results arrive as result.content[0].text (JSON as text).

## On-call discipline — follow in order
1. list_incidents → pick the open one.
2. ack_incident FIRST (silences escalation paging).
3. get_runbook for any runbook attached to the monitor —
   follow the documented procedure over improvising.
4. Investigate on the systems this agent legitimately has
   access to — its own credentials, not PylonMon's.
5. add_incident_note with EVIDENCE (command output, numbers —
   never guesses). Notes appear as <key name>.
6. resolve_incident ONLY when fixed AND verified. A still-down
   auto incident will refuse to resolve — keep noting instead.

## Boundaries
- The agent key cannot create/edit/delete monitors or channels —
  by design. If that seems needed, stop and ask a human.
- If the key 401s it was rotated: ask a human, don't guess.

That file is the whole integration — no SDK, no glue service. The same discipline works pasted into any agent framework that can run curl.

Logs — search, live tail & incident correlation

Ship your application logs to PylonMon and they show up in two places: the Logs → Log streams tab (search as you type, severity and time filters, live tail) and on every incident — the lines from around the moment things broke, on the same screen that paged you. Every plan includes it; retention and daily volume grow with your tier (Free 1 day / 50 MB → Enterprise 90 days / 20 GB).

The one-liner (anything that prints lines)

# JSON lines: msg, level, service, optional ts / trace_id — extra keys become searchable attributes
echo '{"msg":"write failed: no space left","level":"error","service":"api"}' |
  curl -s --data-binary @- -H "Authorization: Bearer YOUR_API_KEY" https://pylonmon.com/api/ingest/logs

# or pipe a journal/file straight in (plain text lines work too — tag the service in the URL):
journalctl -u myapp -f -o cat | curl -s -T - -H "Authorization: Bearer YOUR_API_KEY" \
  "https://pylonmon.com/api/ingest/logs?service=myapp"

OpenTelemetry collector

Point any OTel collector or SDK at /v1/logs (JSON encoding). service.name, timestamps, severity, trace ids, and attributes all come through:

exporters:
  otlphttp/pylonmon:
    endpoint: https://pylonmon.com
    encoding: json
    headers:
      Authorization: "Bearer YOUR_API_KEY"
service:
  pipelines:
    logs:
      exporters: [otlphttp/pylonmon]

Tying logs to monitors

An incident shows the logs for its monitor's log service — by default the slug of the monitor's name (My APImy-api), or set it explicitly in the monitor's edit form (Log service). Ship with matching service values and the "why" appears under every "what".

Ingest is capped per day by plan and never interferes with monitoring — a chatty app gets shed, never a delayed page. Full plan limits on the pricing page.

Prometheus / Alertmanager (paid — 25 alert streams on Pro, 200 on Team+)

There are three ways PylonMon works with Prometheus — monitor a metric, send us your alerts (and drop Alertmanager), or let your own Prometheus scrape us:

1. Monitor a metric. Add a monitor of type Prometheus (Monitors → Add → Prometheus) and pick how the value reaches us:

· We pull it (source: pull). Point the monitor at a /metrics exposition endpoint (an exporter or your app) or a Prometheus /api/v1/query?query=… URL, then pick a metric and a threshold. Use the request-headers field for auth. Like all pull monitors, the target must be publicly reachable.

· You push it to us (source: push). For boxes we can't reach — behind NAT or a firewall, outbound only. Create the monitor with source push; its card then shows a URL like https://pylonmon.com/metric/<id>. Send a number to it from a cron job, script, or exporter — any of these work:

# simplest — the value in the query string
curl "https://pylonmon.com/metric/<id>?value=42"

# or a JSON body, or a plain-number body
curl -d '{"value":42}' https://pylonmon.com/metric/<id>
curl --data-binary 42 https://pylonmon.com/metric/<id>

# or pipe an exporter's output — we read the metric you set on the monitor
curl -s http://localhost:9100/metrics | curl --data-binary @- "https://pylonmon.com/metric/<id>"

# on a schedule, straight from crontab (every 5 min):
*/5 * * * * curl -fsS "https://pylonmon.com/metric/<id>?value=$(df --output=pcent / | tail -1 | tr -dc 0-9)" >/dev/null

Either way the monitor goes down and alerts when the value meets your condition. Push monitors also alert if data simply stops arriving (staleness), so a cron that silently dies is itself an alert. All the usual channels, escalation, incidents, and status pages apply.

2. Send us your Prometheus alerts — with or without Alertmanager. PylonMon speaks the Alertmanager v2 API, so your firing and resolved alerts fan out to every channel in your workspace — email, Discord, Slack, ntfy, SMS — with no per-channel receiver config. Two ways in:

· Drop Alertmanager entirely (recommended). Prometheus already knows how to talk to an Alertmanager — just point it at PylonMon instead of running one. Add PylonMon to Prometheus's own alerting block, send your workspace API key as a Bearer token, and you can delete Alertmanager:

# prometheus.yml
alerting:
  alertmanagers:
    - scheme: https
      authorization:
        type: Bearer
        credentials_file: /etc/prometheus/pylonmon.key   # a file holding your API key
      static_configs:
        - targets: ['pylonmon.com']

Prometheus appends /api/v2/alerts on its own and posts alerts exactly as it would to a real Alertmanager — nothing else to run. A copy-paste, runnable example (Prometheus + node-exporter in Docker) lives in the repo under examples/prometheus-to-pylonmon/.

· Keep your Alertmanager. Already invested in Alertmanager routing and grouping? Add a webhook receiver instead and let it keep doing its job — PylonMon just becomes another receiver. The ingest URL is shown on the Alerts tab:

# alertmanager.yml
receivers:
  - name: pylonmon
    webhook_configs:
      - url: https://pylonmon.com/api/ingest/alertmanager?key=YOUR_KEY

Either way: firing alerts page with the alert name, severity, and summary. Resolved alerts start a ~10-minute confirmation hold before the green all-clear: Prometheus rules love to bounce around their threshold, and a resolve that un-resolves 90 seconds later shouldn't page you twice. During the hold the monitor shows RECOVERING; a re-fire cancels the recovery and counts as a flap on the same incident instead of opening a new one. Once the recovery sticks, the all-clear reports the outage's duration and flap count. Up to 20 alerts are processed per delivery. Here's what a firing alert looks like once it reaches PylonMon:

HighNodeCPU firing
Source: Prometheus · Severity: critical · Summary: CPU on node-01 is 92% busy

Each alert becomes a monitor you control. An inbound alert isn't a fire-and-forget message — PylonMon turns each distinct alertname into its own monitor, so it carries the full notification stack: the alert channels it pages, a severity (SEV1–4, mapped from the alert's severity label), an escalation ladder, on-call rotation, and incidents. Firing drives the monitor down; resolved brings it back after the confirmation hold above — and you tune exactly how any alert pages right on its monitor, the same way you'd configure an HTTP or heartbeat check. A brand-new alert with nothing set is silent until you configure it: tracked and visible, but it won't page anyone by surprise.

Set your defaults once with an alert template. Instead of configuring every alert by hand, create a Prometheus incoming-alert template (Monitors → Add → “Prometheus — incoming-alert template”). The channels, severity, and escalation ladder you put on it become the starting config for every newly-discovered alert — so the first time a new alertname fires, it already routes correctly. The incoming alert's own severity label still wins over the template default, and editing the template later never touches alerts already created (each is independent).

Set it up in two steps.

1. Monitors → Add, and choose the template type:

Type ▾
heartbeat · http/s · tcp · ping · dns · udp · Prometheus
Prometheus — incoming-alert template  ◀ choose this

2. Name it and create it, then open the template and set the alert channels, severity, and escalation ladder every new alert should start with (the panel below):

Illustration — the template, and an alert born from it
Prometheus alert template · last used 3m ago
The starting config every newly-discovered alert inherits.
Alert channels
#oncall-slackPagerDuty
Severity
SEV2 (label overrides)
Incident
auto-create ✓
Escalation ladder
after 5m → #oncall-slack · after 15m → PagerDuty (on-call), repeat every 10m
new alert KafkaLagHigh{severity="critical"} fires the first time
KafkaLagHigh SEV1
Born configured from the template — pages #oncall-slack + PagerDuty, opens a SEV1 incident (the alert's own critical label beat the template's SEV2), and runs the same ladder. No hand-setup.

Real example: Kubernetes (kube-prometheus-stack)

Say you run kube-prometheus-stack on a production cluster. Two things get you from zero to paging in PylonMon:

1. A rule to alert on. A PrometheusRule — here, a node running hot or a disk filling up. Label the ones you want in PylonMon (we route on that label next):

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata: { name: platform, namespace: monitoring }
spec:
  groups:
    - name: platform
      rules:
        - alert: NodeTempHigh
          expr: max by (instance) (node_hwmon_temp_celsius) > 70
          for: 3m
          labels: { severity: warning, pylonmon: "true" }
          annotations:
            summary: '{{ $labels.instance }} running hot — {{ printf "%.0f" $value }}°C'
        - alert: NodeDiskLow
          expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15
          for: 10m
          labels: { severity: critical, pylonmon: "true" }
          annotations:
            summary: '{{ $labels.instance }} disk low — {{ printf "%.0f" $value }}% free'

2. Forward them to PylonMon. Point the bundled Alertmanager at us — route the labelled alerts to a webhook receiver. Keep the key in a mounted secret and send it as a Bearer token:

# kube-prometheus-stack values.yaml
alertmanager:
  alertmanagerSpec:
    secrets: [pylonmon-key]                # mounted at /etc/alertmanager/secrets/…
  config:
    route:
      receiver: "null"
      routes:
        - receiver: pylonmon
          matchers: [ 'pylonmon="true"' ]
    receivers:
      - name: "null"
      - name: pylonmon
        webhook_configs:
          - url: https://pylonmon.com/api/ingest/alertmanager
            send_resolved: true
            http_config:
              authorization: { type: Bearer, credentials_file: /etc/alertmanager/secrets/pylonmon-key/key }

Each alert becomes a monitor seeded from your template — NodeTempHigh and NodeDiskLow show up pre-configured, page your channels, and resolve on their own.

Tip — you probably don't need to write many rules. kube-prometheus-stack already ships ~150 alert rules (KubePodCrashLooping, KubeNodeNotReady, KubePersistentVolumeFillingUp, TargetDown, and more). Instead of a pylonmon="true" label, widen the route to forward the ones you want — e.g. everything severity=~"critical|warning" minus the noisy ones — and you get real coverage with no new rules.

3. Scrape us (any paid plan). PylonMon can also be a Prometheus scrape target, so your own Prometheus can graph and alert on your monitors' up/down state and latency. Turn it on under Settings → Admin → Status page & API → Prometheus metrics (it's off by default), then scrape /api/metrics with one of your API keys — the endpoint is never served unauthenticated, so your metrics stay private to your workspace:

# prometheus.yml
scrape_configs:
  - job_name: pylonmon
    scheme: https
    metrics_path: /api/metrics
    authorization:
      credentials: YOUR_API_KEY
    static_configs:
      - targets: ['pylonmon.com']

You get pylonmon_monitor_up, pylonmon_monitor_latency_ms, pylonmon_monitor_paused, and pylonmon_monitor_last_check_timestamp_seconds, each labelled with the monitor's name, type, and target.

Status badges

Every monitor has a live SVG status badge you can embed in a README, wiki, or status page. Open a monitor's detail to copy the Markdown:

[![PylonMon](https://pylonmon.com/badge/your-badge-token.svg)](https://pylonmon.com)

The badge token is separate from your heartbeat URL, so publishing a badge never exposes a way to ping (or spoof) your monitor.

Security & privacy

Questions? support@pylonmon.com.

Open the console →