PingRobot / Docs
REST API
Create, change and read your monitors, alert contacts, maintenance windows and status pages from scripts, CI pipelines and your own tools. The API is on every plan, including Free.
Quick start
- In the app, open Settings → API keys and create a key. Copy it: it's shown only once.
- Send it as a Bearer token:
curl https://pingrobot.io/api/v1/monitors \
-H "Authorization: Bearer pr_live_…"
Create a monitor:
curl https://pingrobot.io/api/v1/monitors \
-H "Authorization: Bearer pr_live_…" \
-H "Content-Type: application/json" \
-d '{ "type": "http", "name": "Shop", "url": "https://shop.example.com" }'
OpenAPI and MCP
- OpenAPI 3.1: pingrobot.io/api/v1/openapi.json describes every route below. Import it into Postman, Insomnia or Bruno, or generate a client with your favourite OpenAPI generator. It doesn't need a key.
- MCP server: connect Claude, Cursor or VS Code at
https://pingrobot.io/mcpwith the same keys. See the MCP docs.
Basics
| Base URL | https://pingrobot.io/api/v1 |
| Format | JSON in and out. Send Content-Type: application/json with every request that has a body. |
| Times | Unix time in seconds (UTC). |
| Rate limit | 120 requests a minute per key. Over that you get 429 with Retry-After: 60. |
| Plan limits | The same as in the app: monitor count, fastest interval, status pages, alert channels and history. |
Authentication
Every request needs an API key in the Authorization header:
Authorization: Bearer pr_live_9f2c…
Each key has an access level, chosen when you create it. Keys work on your account: the same monitors and pages you see in the app.
| Key | What it can do |
|---|---|
| Read only | GET requests only. Good for dashboards and reporting. |
| Read & write | Everything below: create, change and delete. |
We store only a hash of each key, so a lost key can't be shown again: revoke it in Settings and create a new one. Revoking takes effect at once. You can have up to 10 keys. Keep keys on your server; they are not meant for code that runs in a browser.
A key can't reach billing, account settings or other keys. Those need a signed-in session in the app.
Errors
Errors return a JSON body with a human-readable message. Plan limits also say "upgrade": true:
{ "error": "Your plan includes 10 monitors. Upgrade to add more.", "upgrade": true }
| Status | Meaning |
|---|---|
400 | The request isn't valid. The message says which field. |
401 | The key is missing, wrong or revoked. |
403 | The key is read-only, or the feature isn't on your plan. |
404 | No such route, or the item doesn't exist on your account. |
409 | A conflict, such as a status page address that is taken. |
415 | The body isn't sent as application/json. |
429 | Too many requests. Wait for Retry-After seconds. |
Account
GET /account
Your plan, its limits and what you use. Handy to check a key works.
{
"mode": "live",
"email": "you@example.com",
"plan": "pro",
"limits": { "monitors": 50, "minInterval": 1, "statusPages": 5, "channels": ["email", "webhook", "slack", "discord"], "historyDays": 365 },
"usage": { "monitors": 12, "statusPages": 1 },
"grace": null
}
Monitors
| Request | What it does |
|---|---|
GET /monitors | All monitors, with status, the last 24 hours and 30-day uptime. |
POST /monitors | Creates a monitor. Returns 201 and { "monitor": … }. |
GET /monitors/{id} | One monitor with uptime stats, a response-time chart and recent incidents. Add ?range=7d, 30d, 90d or 1y for longer history (within your plan). |
PATCH /monitors/{id} | Changes the fields you send; the rest stay as they are. |
DELETE /monitors/{id} | Deletes the monitor and its history. |
GET /monitors/{id}/incidents/{incidentId} | One incident with detail, what the failing check saw: the HTTP status, response headers (without cookies), the first 1 KB of the body or the connection, TLS or DNS error, the response time and the second-region re-check. Also its comments. |
POST /monitors/{id}/incidents/{incidentId}/comments | { "body": "Rolled back deploy 4512." } adds a comment (up to 2,000 characters). Comments are private to your account and team. |
DELETE /monitors/{id}/incidents/{incidentId}/comments/{commentId} | Deletes a comment. |
POST /monitors/test | Runs one check with the settings you send, without saving. See below. |
POST /monitors/bulk | { "ids": [12, 13], "action": "pause" }. action: pause, resume, delete, tag or untag (with "tag": "acme"). Up to 250 monitors; returns how many were changed and which were skipped, and why. |
GET /monitors/export | Every monitor as a CSV file (the same columns the import reads). |
POST /monitors/import | { "csv": "…", "dryRun": true } creates monitors from CSV: our export, or another tool's (headers such as Friendly Name, URL/IP, Type, Port, Interval). Up to 500 rows; returns what was created, errors and warnings by line. Use dryRun to preview. |
To pause a monitor send PATCH with { "active": false }, and { "active": true } to resume it.
Monitor fields
| Field | Type | Notes |
|---|---|---|
type | string | http, keyword, port, heartbeat or dns. Required. |
name | string | Up to 80 characters. Shown on status pages. Defaults to the target. |
url | string | http and keyword: a public http(s) URL. |
host, port | string, number | port: host name or IP and TCP port. dns: the domain in host. |
keyword, keywordAbsent | string, boolean | keyword: the text to find in the first 512 KB. With keywordAbsent: true the monitor is down when the text is found. |
dnsType, dnsExpected | string, string[] | dns: record type (A, AAAA, CNAME, MX, NS, TXT or CAA; default A) and up to 10 values that must be present. |
intervalMin | number | 1, 5, 10, 15, 30 or 60 (heartbeats: 5 to 1440). Free plans check every 5 minutes at most. |
timeoutS | number | 3 to 30 seconds. Default 15. |
httpMethod | string | GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS. |
httpAuth | object | { "type": "basic", "username": "ops", "password": "…" } or { "type": "bearer", "token": "…" }, sent as the Authorization header. Responses show only the type and username, never the secret; leave the password or token out on an update to keep it, or send null to remove it. |
httpHeaders | array | [{ "name": "X-Api-Key", "value": "…" }]. Header values are returned as saved, so prefer httpAuth for credentials. |
httpBody | string | Up to 8 KB, for methods other than GET/HEAD. |
expectedCodes | string | Status codes that count as up, like "200-299,301". Default: 2xx. Expecting a 3xx checks the redirect instead of following it. |
jsonRules | array | http only, up to 10: [{ "path": "$.status", "op": "equals", "value": "ok" }]. op: equals, not_equals, contains, not_contains, greater_than, less_than, exists, not_exists, not_empty. |
slowMs | number|null | Counts the check as down when the response is slower (100 to 30,000 ms). |
sslDays | number|null | HTTPS monitors: certificate reminder 7, 14 or 30 days ahead. New HTTPS monitors default to 14. |
expiryDays | number|null | Domain expiry reminder 7, 14, 30 or 60 days ahead. |
alertAfterMin | number | Send the down alert only after this many minutes down: 0, 2, 5, 10, 15, 30 or 60. |
repeatMin | number | "Still down" reminder every 15, 30, 60, 120, 360 or 1440 minutes; 0 for none. |
tags | string[] | Up to 10 labels to group monitors, e.g. by client or environment (lowercased). |
contactIds | number[] | Which alert contacts this monitor uses. Left out on create: every confirmed contact. |
active | boolean | false pauses the monitor. |
Read-only fields in responses: id, status (pending, up, down or paused), statusSince, lastReason, heartbeatUrl and lastPingAt (heartbeats), createdAt, and ssl / domain with the dates we found.
Example: an API monitor with a JSON rule
curl https://pingrobot.io/api/v1/monitors \
-H "Authorization: Bearer pr_live_…" \
-H "Content-Type: application/json" \
-d '{
"type": "http",
"name": "API health",
"url": "https://api.example.com/health",
"intervalMin": 1,
"httpAuth": { "type": "bearer", "token": "YOUR_SERVICE_TOKEN" },
"jsonRules": [{ "path": "$.status", "op": "equals", "value": "ok" }],
"alertAfterMin": 2
}'
Example: a heartbeat for a cron job
curl https://pingrobot.io/api/v1/monitors \
-H "Authorization: Bearer pr_live_…" \
-H "Content-Type: application/json" \
-d '{ "type": "heartbeat", "name": "Nightly backup", "intervalMin": 1440 }'
The response's heartbeatUrl is the address your job calls when it finishes, for example curl -fsS https://pingrobot.io/hb/…. Calling it doesn't need an API key.
Example: pause monitors during a deploy
curl -X PATCH https://pingrobot.io/api/v1/monitors/12 \
-H "Authorization: Bearer pr_live_…" \
-H "Content-Type: application/json" \
-d '{ "active": false }'
For planned work that repeats, a maintenance window is usually better: checks pause and resume on their own.
POST /monitors/test
Send the same fields as for a new monitor. We run one check now and return the result and the first part of the response, without saving anything. It is limited to 10 tests a minute.
Alert contacts
| Request | What it does |
|---|---|
GET /contacts | Your contacts and the channels your plan includes. |
POST /contacts | { "type": "email", "target": "ops@example.com", "label": "On-call" }. type: email, webhook, slack, discord or ntfy (the topic URL, such as https://ntfy.sh/your-topic); target is the address or the https URL. Email contacts get a confirmation link and alert once confirmed. A webhook's response includes its signing secret. |
DELETE /contacts/{id} | Removes the contact from all monitors. |
POST /contacts/{id}/test | Sends a test alert to the contact. |
A new confirmed contact is added to all your monitors. To choose per monitor, set contactIds on the monitor. Webhook payloads and signatures are described in the webhook docs.
Maintenance windows
While a window is active, the monitors it covers aren't checked and send no alerts.
| Request | What it does |
|---|---|
GET /maintenance | All windows, each with active and expired. |
POST /maintenance | Creates a window. |
PATCH /maintenance/{id} | Replaces a window's settings. |
DELETE /maintenance/{id} | Deletes it. |
| Field | Notes |
|---|---|
name | Up to 80 characters. |
repeat | once, daily or weekly. |
monitorIds | The monitors it covers, or null for all of them. |
startAt, endAt | once: start and end (unix seconds), at most 7 days apart. |
timeMin, durationMin | daily / weekly: start as minutes after midnight (0–1439) and length in minutes (5–1440). |
days | weekly: a bitmask of weekdays, Monday = 1, Tuesday = 2, Wednesday = 4 … Sunday = 64. |
tz | An IANA time zone such as Europe/Istanbul. Default UTC. |
# Every Sunday 02:00–03:00 Istanbul time, all monitors
curl https://pingrobot.io/api/v1/maintenance \
-H "Authorization: Bearer pr_live_…" \
-H "Content-Type: application/json" \
-d '{ "name": "Weekly patching", "repeat": "weekly", "days": 64, "timeMin": 120, "durationMin": 60, "tz": "Europe/Istanbul", "monitorIds": null }'
A deploy script can open a one-off window right before it starts (startAt now, endAt in 15 minutes) and delete it when it's done.
Status pages
| Request | What it does |
|---|---|
GET /status-pages | Your pages. |
POST /status-pages | { "title": "Acme status", "slug": "acme", "monitorIds": [12, 13] }. Optional: brand (a hex colour), noindex, showUptime and showIncidents (booleans), password (Pro: 6–128 characters; null removes it; it is never returned, responses say passwordProtected), slaTarget (e.g. 99.9) and slaPeriod (month for the calendar month, or 30d for the last 30 days). |
PATCH /status-pages/{id} | Changes the fields you send. |
DELETE /status-pages/{id} | Deletes the page. |
The page is public at https://pingrobot.io/s/{slug} and https://{slug}.pingrobot.io. Custom domains are set up in the app (see status page domains).
Announcements
| Request | What it does |
|---|---|
GET /status-pages/{id}/announcements | The page's incidents and notices with their updates. |
POST /status-pages/{id}/announcements | Posts one: { "kind": "incident", "status": "investigating", "title": "Checkout errors", "body": "We're looking into it.", "notify": true }. |
POST /status-pages/{id}/announcements/{aid}/updates | Adds an update: { "status": "resolved", "body": "Fixed.", "notify": true }. |
DELETE /status-pages/{id}/announcements/{aid} | Removes it. |
GET /status-pages/{id}/subscribers | The page's email subscribers (up to 1,000), each with confirmed. |
DELETE /status-pages/{id}/subscribers/{sid} | Removes a subscriber. |
kind and its status values: incident (investigating, identified, monitoring, resolved), maintenance (scheduled, in_progress, completed) and info (posted). With "notify": true (Pro), confirmed email subscribers get the update.
Changes
We add fields and routes without notice, so ignore fields you don't know. Anything that would break an existing integration comes as a new version (/api/v2), and v1 keeps working.
Questions or something missing? Get in touch.