IndiePulse Docs / API Reference
API Reference
Read-only REST API for programmatic access to your monitoring data. Available on Hacker plans and above.
Authentication
All API requests require a Bearer token in the Authorization header. Generate an API key in your Settings → API page.
Rate Limits
Rate limits are applied per API key. The response includes X-RateLimit-Limit-Minute and X-RateLimit-Limit-Day headers.
| Plan | Per Minute | Per Day | Max Keys |
|---|---|---|---|
| Hacker | 60 | 5,000 | 2 |
| Maker | 120 | 20,000 | 5 |
| Founder | 300 | 100,000 | 10 |
Monitor States
Monitors expose two state fields. Use monitor_state for dashboards and alerting logic. Use status only when you need the raw consensus result of the latest check round.
| Field | Purpose | Values |
|---|---|---|
| status | Latest consensus check result | UP, DOWN, DEGRADED |
| monitor_state | Operational state machine | PENDING, UP, INVESTIGATING, DOWN, RECOVERING, PAUSED, MAINTENANCE |
Why two fields? A monitor can have a latest check result of UP while its operational state is still RECOVERING (waiting for sustained recovery confirmation). The state machine prevents flapping between UP and DOWN on intermittent issues.
Uptime Calculation
The uptime_percent field returned by the monitor detail endpoint uses time-based calculation:
Only confirmed incidents (type INCIDENT) reduce uptime. Performance degradations (type PERFORMANCE) do not affect the uptime percentage. Returns null when no check data exists for the period.
Event Types
IndiePulse tracks two categories of events. The type field on events distinguishes them:
| Type | Meaning | Affects Uptime? |
|---|---|---|
| INCIDENT | Confirmed downtime — the monitor is unreachable or returning errors | Yes |
| PERFORMANCE | Sustained slow responses exceeding the per-region threshold | No |
| MAINTENANCE | Scheduled maintenance window | No |
The stats_24h object on monitor detail splits check results into down_count (errors affecting uptime) and degraded_count (slow responses that do not affect uptime).
Endpoints
GET/api/v1/monitors
List all monitors in your workspace
Parameters: page (default: 1), per_page (default: 50, max: 100)
Example Response:
{
"data": [
{
"id": "uuid",
"name": "My Website",
"url": "https://example.com",
"status": "UP",
"monitor_state": "UP",
"monitor_type": "http",
"check_interval": 300,
"heartbeat_token": null,
"heartbeat_interval": null,
"heartbeat_grace": null,
"last_heartbeat_at": null,
"created_at": "2026-01-15T10:00:00Z",
"last_checked_at": "2026-02-15T12:00:00Z"
},
{
"id": "uuid",
"name": "Nightly Backup",
"url": null,
"status": "UP",
"monitor_state": "UP",
"monitor_type": "heartbeat",
"check_interval": null,
"heartbeat_token": "a1b2c3d4-...",
"heartbeat_interval": 86400,
"heartbeat_grace": 3600,
"last_heartbeat_at": "2026-02-15T02:00:00Z",
"created_at": "2026-02-01T10:00:00Z",
"last_checked_at": null
}
],
"meta": { "page": 1, "per_page": 50, "total": 3, "total_pages": 1 }
}GET/api/v1/monitors/:id
Get monitor details with 24-hour stats
Parameters: None
Example Response:
{
"data": {
"id": "uuid",
"name": "My Website",
"url": "https://example.com",
"status": "UP",
"monitor_state": "UP",
"monitor_type": "http",
"check_interval": 300,
"stats_24h": {
"total_checks": 1440,
"down_count": 0,
"degraded_count": 12,
"uptime_percent": 100.0
}
}
}
// For heartbeat monitors, stats_24h returns:
// { "total_pings": 24, "successful": 24, "failed": 0 }GET/api/v1/monitors/:id/checks
Check history (HTTP) or ping history (heartbeat) within retention window
Parameters: page (default: 1), per_page (default: 50, max: 100)
Example Response:
// HTTP monitors return checks:
{
"data": [
{
"id": "uuid",
"status": "UP",
"response_time": 142,
"status_code": 200,
"checked_at": "2026-02-15T12:00:00Z",
"region": "iad"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1440, "total_pages": 29, "retention_days": 30 }
}
// Heartbeat monitors return pings:
// "data": [{ "id": "uuid", "pinged_at": "...", "source_ip": "...", "ping_type": "success", ... }]
// "meta": { ..., "data_type": "heartbeat_pings" }GET/api/v1/monitors/:id/domain-health
Domain expiry + DNS record data for a monitor
Parameters: None
Example Response:
{
"data": {
"domain": {
"monitored_domain": "example.com",
"registrar": "Cloudflare, Inc.",
"expiry_date": "2027-08-13T00:00:00Z",
"days_remaining": 543,
"transfer_locked": true,
"check_timestamp": "2026-02-15T05:00:00Z"
},
"dns_baselines": [
{ "record_type": "A", "record_values": ["93.184.216.34"], "ttl": 300, "dnssec_valid": true }
],
"dns_changes": [
{ "record_type": "MX", "change_type": "modified", "previous_values": ["..."], "new_values": ["..."] }
]
}
}GET/POST/api/v1/heartbeat/:token
Send a heartbeat ping. Use from cron jobs, scripts, or CI/CD pipelines.
Parameters: None (token is in the URL path)
Example Response:
{
"ok": true,
"monitor_id": "uuid",
"name": "Nightly Backup"
}POST/api/v1/heartbeat/:token/fail
Signal a failure. Triggers immediate incident creation.
Parameters: None
Example Response:
{
"ok": true,
"monitor_id": "uuid",
"ping_type": "fail"
}GET/api/v1/events
List events (incidents and performance events). Filterable by type and status.
Parameters: page, per_page, type (INCIDENT, PERFORMANCE, MAINTENANCE), status (e.g. "Resolved")
Example Response:
{
"data": [
{
"id": "uuid",
"type": "INCIDENT",
"title": "API Gateway Down",
"status": "Resolved",
"severity": "critical",
"created_at": "2026-02-14T08:00:00Z",
"resolved_at": "2026-02-14T08:45:00Z",
"monitor_id": "uuid"
},
{
"id": "uuid",
"type": "PERFORMANCE",
"title": "api.example.com degraded performance",
"status": "Resolved",
"severity": "minor",
"created_at": "2026-02-14T10:00:00Z",
"resolved_at": "2026-02-14T10:15:00Z",
"monitor_id": "uuid"
}
],
"meta": { "page": 1, "per_page": 50, "total": 12, "total_pages": 1 }
}GET/api/v1/events/:id
Get event details. Incidents include their update timeline.
Parameters: None
Example Response:
{
"data": {
"id": "uuid",
"type": "INCIDENT",
"title": "API Gateway Down",
"status": "Resolved",
"severity": "critical",
"created_at": "2026-02-14T08:00:00Z",
"resolved_at": "2026-02-14T08:45:00Z",
"monitor_id": "uuid",
"updates": [
{ "id": "uuid", "status": "Investigating", "message": "Looking into it", "created_at": "..." },
{ "id": "uuid", "status": "Resolved", "message": "Fixed", "created_at": "..." }
]
}
}GET/api/v1/status
Workspace status summary with monitor, heartbeat, and domain health counts
Parameters: None
Example Response:
{
"data": {
"monitors": { "total": 10, "up": 9, "down": 1, "unknown": 0 },
"heartbeats": { "total": 3, "healthy": 3, "missed": 0 },
"domains": { "monitored": 5, "all_healthy": true, "expiring_soon": [] },
"active_incidents": [
{
"id": "uuid",
"type": "INCIDENT",
"title": "API issues",
"status": "Investigating",
"severity": "major",
"created_at": "..."
}
],
"active_performance_events": [
{
"id": "uuid",
"type": "PERFORMANCE",
"title": "api.example.com degraded performance",
"status": "Investigating",
"created_at": "..."
}
],
"overall_status": "degraded"
}
}Code Examples
curl
# Get workspace status curl -s https://indiepulse.dev/api/v1/status \ -H "Authorization: Bearer ip_live_your_key_here" | jq . # Get a specific monitor with 24h stats curl -s https://indiepulse.dev/api/v1/monitors/MONITOR_ID \ -H "Authorization: Bearer ip_live_your_key_here" | jq '.data.stats_24h'
JavaScript (fetch)
const headers = { 'Authorization': 'Bearer ip_live_your_key_here' };
// List monitors with their operational state
const res = await fetch('https://indiepulse.dev/api/v1/monitors', { headers });
const { data: monitors } = await res.json();
for (const m of monitors) {
console.log(`${m.name}: ${m.monitor_state}`);
// "My Website: UP", "API Server: RECOVERING", etc.
}
// Check overall workspace health
const statusRes = await fetch('https://indiepulse.dev/api/v1/status', { headers });
const { data: status } = await statusRes.json();
console.log(`Status: ${status.overall_status}`);
// "operational", "performance_degraded", or "degraded"Python (requests)
import requests
headers = {"Authorization": "Bearer ip_live_your_key_here"}
# Get workspace status
resp = requests.get("https://indiepulse.dev/api/v1/status", headers=headers)
data = resp.json()["data"]
print(f"Status: {data['overall_status']}")
print(f"Active incidents: {len(data['active_incidents'])}")
print(f"Performance events: {len(data['active_performance_events'])}")
# Get monitor detail with time-based uptime
resp = requests.get("https://indiepulse.dev/api/v1/monitors/MONITOR_ID", headers=headers)
monitor = resp.json()["data"]
stats = monitor["stats_24h"]
print(f"{monitor['name']}: {stats['uptime_percent']}% uptime")Heartbeat Integration
Heartbeat monitors use a dead-man's switch pattern: your job pings IndiePulse on success, and we alert you if the ping doesn't arrive on time. Find your heartbeat URL in the monitor settings page.
crontab
# Ping after successful backup (every day at 2 AM) 0 2 * * * /usr/bin/backup.sh && curl -fsS https://indiepulse.dev/api/v1/heartbeat/YOUR_TOKEN > /dev/null
Node.js
// At the end of your scheduled job
await fetch('https://indiepulse.dev/api/v1/heartbeat/YOUR_TOKEN');
// Signal failure explicitly
await fetch('https://indiepulse.dev/api/v1/heartbeat/YOUR_TOKEN/fail', { method: 'POST' });Python
import requests
# Success ping
requests.get("https://indiepulse.dev/api/v1/heartbeat/YOUR_TOKEN")
# Failure signal
requests.post("https://indiepulse.dev/api/v1/heartbeat/YOUR_TOKEN/fail")Webhooks
Configure webhook endpoints in Settings → Notifications to receive real-time JSON payloads when events occur. Webhooks are sent as POST requests with a Content-Type: application/json header.
Payload Format
{
"event_type": "incident.created",
"timestamp": "2026-02-19T15:30:00.000Z",
"workspace": {
"id": "uuid",
"name": "My Workspace"
},
"monitor": {
"id": "uuid",
"name": "API Server",
"url": "https://api.example.com",
"monitor_type": "http"
},
"event": {
"id": "uuid",
"title": "api.example.com is down",
"status": "Investigating",
"created_at": "2026-02-19T15:30:00.000Z",
"dashboard_url": "https://app.indiepulse.io/monitors/uuid",
"acknowledge_url": "https://app.indiepulse.io/ack?token=xxx&via=webhook"
},
"ai_insight": "High error rate detected across all regions..."
}For resolved events, the event object also includes resolved_at and downtime_seconds. For heartbeat monitors, the monitor object includes a heartbeat sub-object with interval, grace, and last_ping_at fields.
Event Types
| event_type | Description |
|---|---|
| incident.created | A new downtime incident was detected |
| incident.resolved | An incident has been resolved |
| incident.escalated | An incident was escalated after a prolonged duration |
| performance.created | Sustained performance degradation detected |
| performance.resolved | Performance has returned to normal |
| digest.weekly | Weekly health digest summary |
| test | Test event sent during webhook setup |
Signature Verification
Every webhook request includes an X-IndiePulse-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your webhook signing secret. Always verify this signature to ensure the payload was sent by IndiePulse.
import crypto from 'crypto';
function verifyWebhook(body: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
const isValid = verifyWebhook(rawBody, req.headers['x-indiepulse-signature'], SIGNING_SECRET);
if (!isValid) return res.status(401).send('Invalid signature');Error Responses
All errors return a JSON object with an error field.
| Status | Meaning | Example |
|---|---|---|
| 401 | Invalid or missing API key | {"error":"Invalid API key"} |
| 403 | Key suspended or plan downgraded | {"error":"API key is suspended..."} |
| 404 | Resource not found | {"error":"Monitor not found"} |
| 429 | Rate limit exceeded | {"error":"Rate limit exceeded..."} |