OpenStatus vs Better Stack vs Instatus: Status Page and Uptime Monitoring (2026)
TL;DR
OpenStatus is the open-source status page and monitoring platform — synthetic monitoring, status pages, incident management, API checks, self-hostable, built with Next.js. Better Stack (formerly Better Uptime) is the all-in-one observability platform — uptime monitoring, status pages, incident management, on-call scheduling, log management, built for modern DevOps teams. Instatus is the fast, beautiful status page — quick setup, incident communication, integrations, focused purely on status communication. In 2026: OpenStatus for open-source status monitoring, Better Stack for full observability with uptime, Instatus for the fastest beautiful status page setup.
Key Takeaways
- OpenStatus: Open-source, synthetic monitoring, status pages, self-hostable
- Better Stack: All-in-one — uptime + logs + on-call + status pages
- Instatus: Fastest setup — beautiful status pages in minutes
- OpenStatus is free and open-source (Next.js, Cloudflare Workers)
- Better Stack combines uptime monitoring with log management and incident response
- Instatus has the most polished status page design out of the box
OpenStatus
OpenStatus — open-source monitoring:
Setup monitoring
// OpenStatus API — create a monitor:
const response = await fetch("https://api.openstatus.dev/v1/monitor", {
method: "POST",
headers: {
"x-openstatus-key": process.env.OPENSTATUS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "PkgPulse API",
url: "https://api.pkgpulse.com/health",
periodicity: "5m", // Check every 5 minutes
regions: ["iad", "sfo", "ams", "hkg"], // Multi-region
method: "GET",
headers: [{ key: "Accept", value: "application/json" }],
assertions: [
{ type: "status", compare: "eq", target: 200 },
{ type: "latency", compare: "lt", target: 2000 }, // < 2s
],
}),
})
const monitor = await response.json()
console.log(`Monitor created: ${monitor.id}`)
Status page configuration
// Create/update status page:
const statusPage = await fetch("https://api.openstatus.dev/v1/page", {
method: "POST",
headers: {
"x-openstatus-key": process.env.OPENSTATUS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "PkgPulse Status",
slug: "pkgpulse",
description: "Current status of PkgPulse services",
monitors: [1, 2, 3], // Monitor IDs to display
customDomain: "status.pkgpulse.com",
icon: "https://pkgpulse.com/favicon.ico",
}),
})
// Report an incident:
await fetch("https://api.openstatus.dev/v1/incident", {
method: "POST",
headers: {
"x-openstatus-key": process.env.OPENSTATUS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "API Degraded Performance",
status: "investigating", // investigating | identified | monitoring | resolved
monitors: [1],
message: "We're investigating increased latency on the API.",
}),
})
Synthetic monitoring checks
// Advanced monitor with assertions:
await fetch("https://api.openstatus.dev/v1/monitor", {
method: "POST",
headers: {
"x-openstatus-key": process.env.OPENSTATUS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "PkgPulse Search API",
url: "https://api.pkgpulse.com/search?q=react",
periodicity: "10m",
regions: ["iad", "ams"],
method: "GET",
assertions: [
{ type: "status", compare: "eq", target: 200 },
{ type: "latency", compare: "lt", target: 1000 },
{ type: "header", key: "content-type", compare: "contains", target: "application/json" },
],
notifications: [
{ type: "email", target: "ops@pkgpulse.com" },
{ type: "slack", target: "https://hooks.slack.com/..." },
],
degradedAfter: 3, // Mark degraded after 3 failures
timeout: 10000, // 10s timeout
}),
})
// Query monitor results:
const results = await fetch(
"https://api.openstatus.dev/v1/monitor/1/result?limit=50",
{ headers: { "x-openstatus-key": process.env.OPENSTATUS_API_KEY! } }
)
const data = await results.json()
data.forEach((result: any) => {
console.log(`${result.region}: ${result.latency}ms — ${result.statusCode}`)
})
Better Stack
Better Stack — all-in-one observability:
Uptime monitoring
// Better Stack Uptime API — create a monitor:
const response = await fetch("https://uptime.betterstack.com/api/v2/monitors", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
monitor_type: "status",
url: "https://api.pkgpulse.com/health",
pronounceable_name: "PkgPulse API",
check_frequency: 180, // Every 3 minutes (seconds)
regions: ["us", "eu", "as", "au"],
request_timeout: 15,
confirmation_period: 3, // Confirm down for 3 checks
http_method: "get",
expected_status_codes: [200],
request_headers: [
{ name: "Accept", value: "application/json" },
],
// Alert policies:
policy_id: "policy-123",
follow_redirects: true,
verify_ssl: true,
ssl_expiration: 30, // Alert 30 days before SSL expiry
}),
})
const monitor = await response.json()
console.log(`Monitor: ${monitor.data.id} — ${monitor.data.attributes.url}`)
Incident management
// Create an incident:
const incident = await fetch("https://uptime.betterstack.com/api/v2/incidents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
requester_email: "ops@pkgpulse.com",
name: "Database Connection Issues",
summary: "PostgreSQL connections timing out, causing API errors",
description: "Investigating connection pool exhaustion on the primary database",
call: true, // Trigger phone call to on-call
sms: true, // Send SMS
email: true, // Send email
push: true, // Push notification
}),
})
// Update incident timeline:
await fetch(`https://uptime.betterstack.com/api/v2/incidents/${incident.data.id}/timeline`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "Identified root cause: connection pool limit too low. Scaling up.",
}),
})
// Resolve incident:
await fetch(`https://uptime.betterstack.com/api/v2/incidents/${incident.data.id}/resolve`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
"Content-Type": "application/json",
},
})
Status page and on-call
// Create status page:
await fetch("https://uptime.betterstack.com/api/v2/status-pages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
company_name: "PkgPulse",
company_url: "https://pkgpulse.com",
subdomain: "pkgpulse", // pkgpulse.betteruptime.com
custom_domain: "status.pkgpulse.com",
timezone: "America/New_York",
subscribable: true, // Allow email subscriptions
hide_from_search_engines: false,
}),
})
// Set up on-call schedule:
await fetch("https://uptime.betterstack.com/api/v2/on-call-calendars", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BETTERSTACK_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "PkgPulse Engineering On-Call",
default_calendar_attributes: {
on_call_users: [
{ id: "user-1", weekday_from: "monday", weekday_to: "friday" },
{ id: "user-2", weekday_from: "saturday", weekday_to: "sunday" },
],
},
}),
})
// Heartbeat monitor (for cron jobs):
// POST https://uptime.betterstack.com/api/v1/heartbeat/{heartbeat_id}
// If no ping received within the expected period → alert
await fetch(`https://uptime.betterstack.com/api/v1/heartbeat/${HEARTBEAT_ID}`)
Log management
// Send logs to Better Stack (Logtail):
import Logtail from "@logtail/node"
const logtail = new Logtail(process.env.LOGTAIL_TOKEN!)
// Structured logging:
logtail.info("Package data synced", {
packages_count: 150,
duration_ms: 2340,
source: "npm-registry",
})
logtail.error("API request failed", {
url: "/api/packages/react",
status: 500,
error: "Database connection timeout",
request_id: "req-abc-123",
})
// Flush logs (important before process exit):
await logtail.flush()
// Express middleware for request logging:
import { LogtailTransport } from "@logtail/winston"
import winston from "winston"
const logger = winston.createLogger({
transports: [new LogtailTransport(logtail)],
})
app.use((req, res, next) => {
const start = Date.now()
res.on("finish", () => {
logger.info("HTTP Request", {
method: req.method,
path: req.path,
status: res.statusCode,
duration_ms: Date.now() - start,
})
})
next()
})
Instatus
Instatus — beautiful status pages:
Status page setup
// Instatus API — create a status page:
const response = await fetch("https://api.instatus.com/v1/pages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "PkgPulse",
subdomain: "pkgpulse", // pkgpulse.instatus.com
status: "UP",
logoUrl: "https://pkgpulse.com/logo.png",
faviconUrl: "https://pkgpulse.com/favicon.ico",
websiteUrl: "https://pkgpulse.com",
language: "en",
useLargeHeader: true,
subscribable: true,
}),
})
const page = await response.json()
const pageId = page.id
Components and incidents
// Add components (services to monitor):
const component = await fetch(`https://api.instatus.com/v1/${pageId}/components`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "API",
description: "PkgPulse REST API",
status: "OPERATIONAL", // OPERATIONAL | DEGRADEDPERFORMANCE | PARTIALOUTAGE | MAJOROUTAGE | UNDERMAINTENANCE
order: 1,
showUptime: true,
grouped: false,
}),
})
// Add more components:
const components = [
{ name: "Website", description: "Main website", order: 2 },
{ name: "Search", description: "Package search engine", order: 3 },
{ name: "CDN", description: "Static asset delivery", order: 4 },
]
for (const comp of components) {
await fetch(`https://api.instatus.com/v1/${pageId}/components`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ...comp, status: "OPERATIONAL", showUptime: true }),
})
}
// Create an incident:
const incident = await fetch(`https://api.instatus.com/v1/${pageId}/incidents`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Search Index Rebuild Delay",
message: "Search results may be stale while the index rebuilds.",
components: [component.id],
started: new Date().toISOString(),
status: "INVESTIGATING", // INVESTIGATING | IDENTIFIED | MONITORING | RESOLVED
notify: true, // Notify subscribers
statuses: [
{
id: component.id,
status: "DEGRADEDPERFORMANCE",
},
],
}),
})
Incident updates and integrations
// Update incident with progress:
await fetch(`https://api.instatus.com/v1/${pageId}/incidents/${incident.id}/incident-updates`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
message: "Root cause identified — index shard allocation issue. Rebalancing in progress.",
status: "IDENTIFIED",
started: new Date().toISOString(),
notify: true,
statuses: [
{ id: component.id, status: "DEGRADEDPERFORMANCE" },
],
}),
})
// Resolve incident:
await fetch(`https://api.instatus.com/v1/${pageId}/incidents/${incident.id}/incident-updates`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
message: "Search index rebuild complete. All services operational.",
status: "RESOLVED",
started: new Date().toISOString(),
notify: true,
statuses: [
{ id: component.id, status: "OPERATIONAL" },
],
}),
})
// Schedule maintenance:
await fetch(`https://api.instatus.com/v1/${pageId}/incidents`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INSTATUS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Scheduled Database Maintenance",
message: "We'll be performing routine database maintenance. Brief downtime expected.",
status: "NOTSTARTEDYET",
notify: true,
started: "2026-03-15T02:00:00Z",
components: [component.id],
statuses: [{ id: component.id, status: "UNDERMAINTENANCE" }],
}),
})
// Webhook integration (receive Instatus events):
// POST /api/webhooks/instatus
app.post("/api/webhooks/instatus", (req, res) => {
const { incident, page } = req.body
if (incident.status === "RESOLVED") {
// Post to Slack, update internal dashboards, etc.
notifySlack(`✅ ${incident.name} resolved on ${page.name}`)
}
res.sendStatus(200)
})
Feature Comparison
| Feature | OpenStatus | Better Stack | Instatus |
|---|---|---|---|
| Open-source | ✅ | ❌ | ❌ |
| Self-hosted | ✅ | ❌ | ❌ |
| Status pages | ✅ | ✅ | ✅ |
| Uptime monitoring | ✅ (synthetic) | ✅ (multi-protocol) | ❌ (3rd party) |
| Incident management | ✅ | ✅ | ✅ |
| On-call scheduling | ❌ | ✅ | ❌ |
| Log management | ❌ | ✅ (Logtail) | ❌ |
| Heartbeat monitors | ✅ | ✅ | ❌ |
| Multi-region checks | ✅ | ✅ | ❌ |
| Custom domains | ✅ | ✅ | ✅ |
| Subscriber notifications | ✅ | ✅ | ✅ |
| Scheduled maintenance | ✅ | ✅ | ✅ |
| API access | ✅ | ✅ | ✅ |
| Integrations | Slack, Discord, email | 100+ (Slack, PagerDuty, etc.) | Slack, Discord, email |
| Free tier | Open-source (unlimited) | 10 monitors | 1 status page |
| Pricing | Free / Cloud plans | From $29/month | From $20/month |
When to Use Each
Use OpenStatus if:
- Want an open-source, self-hostable monitoring solution
- Need synthetic monitoring with multi-region checks
- Prefer owning your monitoring infrastructure
- Building on Cloudflare Workers / Next.js ecosystem
Use Better Stack if:
- Need all-in-one observability (uptime + logs + on-call + status page)
- Want on-call scheduling and escalation policies
- Need log management alongside uptime monitoring
- Building a DevOps workflow with incident response
Use Instatus if:
- Need the fastest, most beautiful status page setup
- Want a focused status communication tool (not full monitoring)
- Need subscriber notifications and maintenance windows
- Prefer simplicity over feature breadth
Methodology
Feature comparison based on OpenStatus, Better Stack, and Instatus platforms and pricing as of March 2026.
Compare DevOps tooling and monitoring libraries on PkgPulse →