Skip to content

API Reference ​

Pingniner exposes three separate things, and it helps to know which one you want:

Called byPurpose
Management APIYour codeRead and manage monitors
Heartbeat pingYour codeReport that a scheduled job ran
Agent ingestThe agentReport server and workstation metrics

The management API is authenticated with an API token. The two ingest endpoints are not — the key in the URL is the credential.

If you would rather not write HTTP calls at all, Claude Code can drive the management API for you.

Management API ​

https://app.pingniner.com/api/v1

Every request needs an API token as a bearer token:

sh
curl https://app.pingniner.com/api/v1/monitors \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Accept: application/json"

The token decides which account you are working on. There is no account parameter, and adding one changes nothing.

The seven monitor types ​

Wherever an endpoint takes a {type}, it is one of:

site · server · workstation · heartbeat · port · ping · blacklist

They do not all behave alike, and two differences matter when writing against the API:

  • Sites, ports, pings and blacklists are checked by Pingniner from a monitoring location, on an interval your plan allows. They need a location_id and a frequency.
  • Heartbeats work the other way round: your job calls us. Their frequency describes your schedule, not our checking, and is not limited by your plan.
  • Servers and workstations report from an installed agent. They have metrics rather than uptime, and creating one only makes the record — nothing is monitored until the agent is installed.

Endpoints ​

MethodPathNeeds
GET/accountRead
GET/monitorsRead
GET/monitors/{type}/{id}Read
GET/monitors/{type}/{id}/historyRead
GET/monitors/{type}/{id}/uptimeRead
GET/alertsRead
GET/servers/{id}/metricsRead
GET/locationsRead
GET/groupsRead
POST/monitors/{type}Write
PATCH/monitors/{type}/{id}Write
POST/monitors/{type}/{id}/pauseWrite
POST/monitors/{type}/{id}/resumeWrite

There is deliberately no delete endpoint. See API tokens.

Account and limits ​

GET /account is worth calling before you create anything — it tells you whether your plan has room and which check intervals it allows.

json
{
  "data": {
    "id": 42,
    "name": "Acme Ltd",
    "subscribed": true,
    "monitors": { "used": 12, "limit": 30, "can_add": true },
    "status_pages": { "used": 1, "limit": 3 },
    "default_retention_days": 30,
    "available_check_intervals_minutes": [1, 2, 3, 4, 5, 10, 15, 30, 60, 1440]
  }
}

available_check_intervals_minutes is the complete set of values frequency will accept when creating or updating a site, port, ping or blacklist. Anything else is refused.

TIP

On the plans that offer sub-minute checking the list starts with 0, which means every 30 seconds rather than zero minutes. It is the one value in there that is not a count of minutes.

Listing monitors ​

GET /monitors returns every monitor, grouped by type. Without a type filter all seven keys are present, including empty arrays for the types you do not use — so you can index data.site without checking it exists first.

Filters: type, status, group_id, tag, search.

sh
curl "https://app.pingniner.com/api/v1/monitors?type=site&status=Down" \
  -H "Authorization: Bearer YOUR_TOKEN"
json
{
  "data": {
    "site": [
      {
        "id": "9d1f2c34-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
        "type": "site",
        "name": "Marketing site",
        "status": "Up",
        "paused": false,
        "target": { "url": "https://example.com" },
        "group_id": null,
        "tags": ["production"],
        "retention_days": 30,
        "last_seen_at": "2026-08-28T14:32:00+00:00",
        "created_at": "2026-01-04T09:12:00+00:00",
        "location_id": 3,
        "check_interval_minutes": 5
      }
    ]
  },
  "meta": { "total": 1 }
}

target is shaped by type, so you can tell a URL from a hostname without guessing: {"url": …} for sites, {"host": …, "port": …} for ports, {"host": …} for pings, {"ip_address": …} for blacklists, and {} for the three with no external target.

Heartbeats add expected_every_minutes and grace_minutes. Servers and workstations add platform, agent_key and agent_reporting.

Status, and what "paused" means ​

status is the monitor's health: Up, Down, Degraded, Maintenance or Unknown.

A monitor with status Maintenance is paused, which the paused field also reports plainly. Read that carefully:

WARNING

Pausing suppresses alerting. It does not stop the checks. Pingniner keeps contacting the target and keeps recording history — only the alerts stop. If you need us to stop touching a host entirely, pausing is not the tool.

History ​

GET /monitors/{type}/{id}/history returns individual check results, newest first.

Parameters: limit (default 100, maximum 1000), from, to.

Readings differ by type, so they are nested under readings rather than pretending to a common shape:

json
{
  "data": [
    {
      "id": 91827364,
      "timestamp": "2026-08-28 14:32:00",
      "location_id": 3,
      "readings": {
        "latency": 187,
        "online": 1,
        "http_status_code": 200,
        "namelookup_time": 12,
        "connect_time": 41,
        "pretransfer_time": 88,
        "starttransfer_time": 175
      }
    }
  ]
}

Ask for the smallest window that answers your question. These are the largest tables in Pingniner, and the response deliberately leaves out the heaviest columns — a site's full response body, a server's per-disk and per-process JSON.

Uptime ​

GET /monitors/{type}/{id}/uptime?period=week, where period is today, week, month or year. Defaults to week.

json
{
  "data": {
    "monitor_id": "9d1f2c34-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
    "monitor_type": "site",
    "period": "week",
    "uptime_percent": 99.9821
  }
}

Servers and workstations return 422 — they record metrics rather than up/down checks, so there is no uptime figure to give. Use /servers/{id}/metrics instead.

Alerts ​

GET /alerts returns alerts grouped by monitor type, newest first. An alert with "open": true has not ended yet, which is what to look at for what is wrong right now.

Parameters: type, limit (default 50, maximum 500), open.

sh
curl "https://app.pingniner.com/api/v1/alerts?open=true" \
  -H "Authorization: Bearer YOUR_TOKEN"

Creating a monitor ​

POST /monitors/{type} with the fields that type needs.

sh
curl -X POST https://app.pingniner.com/api/v1/monitors/site \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Marketing site",
    "url": "https://example.com",
    "location_id": 3,
    "frequency": 5
  }'
TypeRequired
sitename, url, location_id, frequency
portname, host, port, location_id, frequency
pingname, host, location_id, frequency
blacklistname, ip_address, location_id, frequency
heartbeatname, frequency, grace
server, workstationname, type (Linux, Windows, macOS or BSD)

Optional on every type: group_id, tags, notes. Sites also accept method, expected_status_code, timeout, connect_timeout, useragent, postdata, headers, additional_locations, certificate_check, domain_check, broken_links_check, lighthouse_check and lighthouse_schedule.

Get location_id from GET /locations and group_id from GET /groups.

A new monitor arrives configured exactly as one created in the interface — the same default triggers, the same retention from your plan, and its first check already queued.

Fields that Pingniner itself writes — status, last_checked, certificate details, agent readings — are ignored if you send them. You cannot declare your own site up.

TIP

Creating a server or workstation creates the record only. Nothing is monitored until the agent is installed on the machine using the agent_key in the response.

Updating, pausing and resuming ​

PATCH /monitors/{type}/{id} changes only the fields you send.

sh
curl -X PATCH https://app.pingniner.com/api/v1/monitors/site/YOUR_ID \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"frequency": 1}'

POST /monitors/{type}/{id}/pause and .../resume move a monitor in and out of maintenance. Both return the monitor and a note describing what actually changed.

Errors ​

StatusMeans
401No token, or an expired or revoked one
403The token lacks the ability this endpoint needs, or is no longer valid for its account
404No such monitor on this account
422Understood but refused — validation, or a plan limit
429Rate limited

A monitor belonging to another account returns 404, not 403. Telling you that you may not see something still confirms it exists.

Plan limits come back as 422 with the numbers, so you can say what to do about it rather than only that it failed:

json
{
  "message": "This account is using 30 of its 30 available monitors. Remove a monitor or upgrade the plan to add another.",
  "monitors": { "used": 30, "limit": 30 }
}

Validation failures use the standard shape:

json
{
  "message": "The frequency field is invalid.",
  "errors": { "frequency": ["The selected frequency is invalid."] }
}

A common cause is asking for a check interval your plan does not include — call GET /account for the ones it does.

Heartbeat ping ​

This is the endpoint you integrate with to report that a scheduled job ran. Full documentation with code examples for cron, PHP, Python, Ruby, Node.js, Perl and Bash is on the Heartbeats page; this is the reference.

Endpoint

GET https://ingest.pingniner.com/api/heartbeat/{key}

{key} is the heartbeat monitor's ID, shown on its Overview page.

Optional query parameters

ParameterTypeDescription
runtimenumberHow long the job took, in seconds
memorynumberPeak memory used, in MB
messagestringA short note, URL-encoded

Example

sh
curl -fsS -m 10 --retry 5 -o /dev/null \
  "https://ingest.pingniner.com/api/heartbeat/YOUR_KEY?runtime=42&memory=128&message=Backup%20complete"

Response

json
{ "success": true, "message": "Received successfully" }

An unknown key returns:

json
{ "success": false, "message": "Heartbeat not found" }

Authentication: none. The key in the URL is the credential — this is not an API token, and the two are not interchangeable.

Pingniner also records the source IP address and user agent of each ping, both visible in the heartbeat's history.

TIP

Call the ping URL after your job succeeds, not at the start. A heartbeat that fires before the work happens only proves the job started.

Agent ingest ​

These endpoints exist for the agent and are documented for completeness. You should not need to call them yourself — install the agent instead.

POST https://ingest.pingniner.com/api/server/{key}
POST https://ingest.pingniner.com/api/workstation/{key}

{key} is the server or workstation monitor's key, which is also the agent_key returned when you create one through the management API. The body is a JSON payload produced by the systeminformation library — see What the Agent Collects for the fields.

Authentication: none. The key in the URL is the credential.

WARNING

Because these endpoints are unauthenticated beyond the key, anyone holding a monitor key can post data to that monitor. Treat keys like passwords: do not paste install commands containing real keys into tickets, shared documents or chat.

Outbound webhooks ​

Pingniner can call your endpoint when an incident opens or closes. That is configured per user as a notification channel, not here.

The request is a POST with a User-Agent of Pingniner and a body of:

json
{
  "message": "Incident: My Website - Offline @ 2026-08-27 14:32:00"
}

Use this to reach anything without a built-in integration — PagerDuty, Opsgenie or your own automation. Slack, Microsoft Teams, Google Chat and Discord all have dedicated channels of their own; see Notification Channels.

Status page subscriptions ​

Status page subscribers manage themselves through the public page. There is no endpoint for adding subscribers programmatically, deliberately — subscriptions are double opt-in so that nobody can be signed up by someone else.

Network requirements ​

Ingest goes to one host, the management API to another:

HostPortDirectionUsed by
ingest.pingniner.com443 (HTTPS)Outbound from your infrastructureHeartbeat pings, the agent
app.pingniner.com443 (HTTPS)Outbound from your infrastructureManagement API, Claude Code

Add them to any egress allowlist. Nothing needs to be open inbound.

If you are allowlisting Pingniner's outbound checks — the addresses our site, ping and port monitors connect from — see the IP list in the FAQ.

Monitoring done right.