API Reference

Programmatic access to your PoppaPing monitors, incidents, and status pages.

Getting Started

1. Get your API key — Go to Dashboard → API and create a key. Copy it immediately; it won't be shown again.

2. Make your first request

curl -H "Authorization: Bearer pp_live_YOUR_KEY_HERE" \
  https://poppaping.com/api/v1/monitors

3. Parse the response

{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "name": "Production API",
      "url": "https://api.example.com/health",
      "current_status": "up",
      "response_time_ms": 142,
      ...
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 3, "total_pages": 1 }
}

Authentication

All API requests require an API key. Pass it via either header:

# Option 1: Authorization header (recommended)
Authorization: Bearer pp_live_a8Xk3m...

# Option 2: X-API-Key header
X-API-Key: pp_live_a8Xk3m...

Keys are created in Dashboard → API with configurable scopes:

Scope Access
monitors:readList & view monitors, uptime, checks
monitors:writeCreate, update, delete, pause, resume monitors
incidents:readList & view incidents
status-pages:readList & view status pages
alerts:readList alert channels

Account endpoints (/account, /account/usage) require any valid key, no specific scope.

Rate Limits

Rate limits are per API key, using a sliding 60-second window.

Plan Requests / minute
Free60
Starter120
Growth180
Pro300
Business600

Every response includes rate limit headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1708531200

When exceeded, you'll receive 429 Too Many Requests:

{ "error": "rate_limit_exceeded", "message": "Rate limit exceeded. Retry after 15 seconds." }

Error Codes

All errors return JSON with error and message fields:

{ "error": "not_found", "message": "Monitor not found." }
Status Error Code Meaning
400validation_errorInvalid input data
401unauthorizedMissing or invalid API key
401key_expiredAPI key has expired
403insufficient_scopeKey lacks required scope
403plan_limit_reachedPlan quota exceeded
404not_foundResource doesn't exist
429rate_limit_exceededToo many requests

Account

GET /api/v1/account

Get current account info and plan details. No scope required.

curl -H "Authorization: Bearer $KEY" https://poppaping.com/api/v1/account
GET /api/v1/account/usage

Get current billing period usage. No scope required.

// Response
{
  "monitors_used": 3, "monitors_active": 2, "monitors_limit": 5,
  "checks_last_24h": 1440, "plan": "starter"
}

Monitors

GET /api/v1/monitors

List all monitors. Scope: monitors:read

Params: ?page=1&per_page=25

curl -H "Authorization: Bearer $KEY" https://poppaping.com/api/v1/monitors
POST /api/v1/monitors

Create a new monitor. Scope: monitors:write

curl -X POST -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production API","url":"https://api.example.com/health","interval_seconds":60}' \
  https://poppaping.com/api/v1/monitors

Body fields: name (required), url (required), method (GET), interval_seconds, timeout_seconds (30), headers ({}), regions ([])

GET /api/v1/monitors/:id

Get single monitor with 30-day uptime. Scope: monitors:read

PATCH /api/v1/monitors/:id

Update monitor settings. Only include fields to change. Scope: monitors:write

curl -X PATCH -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"interval_seconds":30}' \
  https://poppaping.com/api/v1/monitors/MONITOR_ID
DELETE /api/v1/monitors/:id

Delete a monitor permanently. Scope: monitors:write

POST /api/v1/monitors/:id/pause   POST /api/v1/monitors/:id/resume

Pause or resume monitoring. Scope: monitors:write

GET /api/v1/monitors/:id/uptime

Get uptime statistics. Scope: monitors:read

Params: ?period=24h|7d|30d|90d (default: 30d)

// Response
{
  "data": {
    "monitor_id": "...", "period": "30d",
    "uptime_percentage": 99.97, "total_checks": 43200,
    "up_checks": 43187, "down_checks": 13,
    "avg_response_time_ms": 142.3
  }
}
GET /api/v1/monitors/:id/checks

Get recent check results. Scope: monitors:read

Params: ?page=1&per_page=100 (max 500)

Incidents

GET /api/v1/incidents

List all incidents. Scope: incidents:read

Params: ?status=open|resolved&monitor_id=UUID&page=1&per_page=25

GET /api/v1/incidents/:id

Get incident details with check timeline. Scope: incidents:read

// Response includes timeline of checks during the incident
{
  "data": {
    "id": "...", "monitor_id": "...",
    "started_at": "2025-02-21T14:32:00+00:00",
    "resolved_at": "2025-02-21T14:45:00+00:00",
    "cause": "Connection timed out",
    "status": "resolved",
    "timeline": [ { "status": "down", "region": "chicago", ... }, ... ]
  }
}

Status Pages

GET /api/v1/status-pages

List your status pages. Scope: status-pages:read

GET /api/v1/status-pages/:id

Get status page with current component statuses. Scope: status-pages:read

Alerts

GET /api/v1/alerts

List configured alert channels (email, webhooks). Scope: alerts:read

Webhooks

PoppaPing sends webhook events when monitor status changes. Configure webhook URLs in Alerts.

Event Types

Event Trigger
monitor.downMonitor transitions to DOWN
monitor.recoveredMonitor recovers from DOWN to UP
incident.createdNew incident opened
incident.resolvedIncident resolved

Payload Format

{
  "event": "monitor.down",
  "timestamp": "2025-02-21T14:32:00+00:00",
  "data": {
    "monitor": {
      "id": "...", "name": "Production API",
      "url": "https://api.example.com/health",
      "current_status": "down", ...
    },
    "incident": {
      "id": "...", "started_at": "2025-02-21T14:32:00+00:00",
      "cause": "Connection timed out", "status": "open"
    }
  }
}

Signature Verification

Every webhook includes an X-PoppaPing-Signature header — an HMAC-SHA256 of the raw request body using your webhook secret (found in Settings).

Python

import hmac, hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Node.js

const crypto = require('crypto');

function verifySignature(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(signature)
  );
}

Questions? Email [email protected]