You deployed at 11pm. The pipeline is green. Health checks pass. Twenty minutes later a user reports 500s on /api/orders.
Monitoring API uptime from the command line is the fastest way to watch an endpoint during a rollout, debug a flaky dependency, or confirm a fix without opening another browser tab or paying for a dashboard you use twice a year.
This guide covers when a curl loop is enough, when you want something better, and how to set up upstatus for live terminal monitoring with POST support, degraded detection, and CI-friendly output.
TL;DR
| Goal | What to use |
|---|---|
| One request, exit immediately | curl -sf https://api.example.com/health |
| Watch endpoints live during a deploy | upstatus https://api.example.com |
| POST health check with a JSON body | upstatus https://api.example.com/health -m POST -b '{"probe":"full"}' |
| Flag slow but alive APIs | upstatus https://api.example.com -d 1000 |
| Logs/CI without terminal clearing | upstatus https://api.example.com --no-clear |
| Export stats when you stop (Ctrl+C) | upstatus https://api.example.com --export json -o report.json |
| Single smoke test in GitHub Actions | Programmatic Monitor.check() (see below) |
Why monitor from the terminal?
SaaS uptime tools (UptimeRobot, Better Stack, Pingdom) are great for 24/7 alerting. You should use them for production.
But they are slow for right now problems:
- You just deployed and want to watch response times tick up in real time
- A third-party API is flaky and you need to see failure patterns over 10 minutes
- You are on a VPN or SSH session without dashboard access
- You want zero accounts, zero config files, zero monthly bill
A CLI monitor runs locally. No signup. No agent. No webhook setup. Install, paste URLs, watch.
Option 1: curl (the baseline)
For a single check that exits immediately, curl wins:
curl -sf -o /dev/null -w "%{http_code} %{time_total}s\n" https://api.example.com/health
-s silences progress. -f fails on HTTP 4xx/5xx. -w prints status and timing.
A bash loop for repeated checks:
while true; do
curl -sf -o /dev/null -w "$(date +%H:%M:%S) %{http_code} %{time_total}s\n" \
https://api.example.com/health || echo "$(date +%H:%M:%S) FAILED"
sleep 30
done
This works. It also gets messy fast:
- No uptime percentage across checks
- No "degraded" state when the API is slow but returning 200
- No POST body support without more flags
- No structured export when you stop
- Hard to monitor three URLs in one readable view
When you outgrow the loop, reach for a small CLI tool.
Option 2: upstatus (live multi-URL monitoring)
upstatus is a Node.js CLI for watching HTTP endpoints from your terminal. It tracks response times, calculates uptime percentage, and labels each URL as up, degraded, or down.
Install
npm install -g upstatus
No install? Use npx:
npx upstatus https://api.github.com
Monitor one or more URLs
upstatus https://api.example.com
Multiple endpoints, default 30-second interval:
upstatus https://api.example.com https://status.example.com https://api.github.com
Custom interval (60 seconds):
upstatus https://api.example.com -i 60
What you see
The terminal refreshes with live stats (powered by logfx):
✅ https://api.example.com ........ 187ms (99.8% uptime)
⚠️ https://slow-api.com ......... 1450ms (98.7% uptime) [degraded]
🔴 https://down.example.com ..... DOWN (last check failed)
Press Ctrl+C to stop. upstatus prints a final summary and can export results (see below).
POST health checks
Many APIs expose GET /health that always returns 200 even when the database is down. Real probes use POST with a body.
upstatus https://api.example.com/health \
-m POST \
-b '{"check":"deep"}'
Supported methods: GET, POST, PUT, PATCH, DELETE.
For auth headers or advanced config, use the programmatic API (see CI section).
Degraded vs down
Down means the request failed, timed out, or returned an unexpected status code.
Degraded means the API responded with the expected status, but slower than your threshold. Default threshold is 2000ms.
# Mark as degraded if response exceeds 1000ms
upstatus https://api.example.com -d 1000
This matters during rollouts. A service can be "up" while latency spikes. Catching degraded early is often the difference between a rollback and an outage.
| Status | Meaning |
|---|---|
up |
Expected status code, under threshold |
degraded |
Expected status code, over threshold |
down |
Error, timeout, or wrong status code |
Export results on exit
When you stop monitoring, export stats for an incident report or Slack update:
upstatus https://api.example.com --export json -o uptime-report.json
CSV works too:
upstatus https://api.example.com --export csv -o uptime-report.csv
Exports run on Ctrl+C. Handy after a 20-minute deploy watch when you need numbers, not screenshots.
JSON mode and --no-clear for logs and CI
--no-clear
By default, upstatus clears the terminal on each refresh (nice locally, bad in CI logs). Disable it:
upstatus https://api.example.com --no-clear
Use this in GitHub Actions log output, Docker containers, or any non-TTY environment.
--json
JSON mode prints stats as JSON every 5 check cycles:
upstatus https://api.example.com --json --no-clear
Example output shape:
[
{
"url": "https://api.example.com",
"name": "api.example.com",
"checks": 5,
"uptime": 100,
"avgResponseTime": 214,
"lastCheck": {
"status": "up",
"statusCode": 200,
"responseTime": 198
}
}
]
Note: The CLI is designed for continuous monitoring, not one-shot exits. For deploy smoke tests that must pass or fail in under 5 seconds, use curl or the programmatic API below.
CI/CD: smoke test after deploy
Quick smoke test (curl)
Still the right default for "did deploy work?":
curl -sf https://api.example.com/health
Add to GitHub Actions:
- name: Smoke test API
run: curl -sf https://api.example.com/health
Smoke test with POST + retries (upstatus API)
When you need POST bodies, custom expected status codes, or retry logic, use Monitor.check() from the library. One request, explicit exit code.
Create scripts/smoke-check.mjs:
import { Monitor } from 'upstatus'
const url = process.env.API_URL
if (!url) {
console.error('Set API_URL environment variable')
process.exit(1)
}
const monitor = new Monitor({
url,
method: 'POST',
body: JSON.stringify({ check: 'deep' }),
expectedStatus: [200, 201],
maxRetries: 2,
timeout: 10000,
})
const result = await monitor.check()
if (result.status === 'down') {
console.error(`Smoke check failed: ${result.error ?? result.statusCode}`)
process.exit(1)
}
console.log(`Smoke check ${result.status}: ${result.responseTime}ms`)
GitHub Actions workflow:
name: Post-deploy smoke test
on:
workflow_dispatch:
push:
branches: [main]
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install upstatus
run: npm install upstatus
- name: Smoke check production API
run: node scripts/smoke-check.mjs
env:
API_URL: ${{ secrets.PROD_API_URL }}
For headers (Bearer tokens, API keys), pass headers in the Monitor config:
const monitor = new Monitor({
url: process.env.API_URL,
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
maxRetries: 2,
})
Store tokens in GitHub Actions secrets, not in the workflow file.
Watch during a long deploy (optional)
For a manual or scheduled job that watches an endpoint for several minutes:
- name: Watch API for 3 minutes after deploy
run: timeout 180 npx upstatus ${{ secrets.PROD_API_URL }} --no-clear --json || true
timeout sends SIGTERM; upstatus exports on shutdown if --export is set. This is optional. Most teams use SaaS alerting for 24/7 coverage and CLI tools during incidents.
curl vs upstatus vs SaaS (comparison)
| curl loop | upstatus CLI | UptimeRobot / Better Stack | |
|---|---|---|---|
| Setup time | 0 min | 30 sec | 10-15 min |
| Cost | Free | Free | Free tier, then paid |
| Multi-URL dashboard in terminal | Manual | Built-in | Web UI |
| Uptime % over session | DIY | Built-in | Built-in |
| Degraded (slow) detection | DIY |
-d flag |
Varies by plan |
| POST health checks | Verbose flags | -m POST -b |
Supported |
| 24/7 alerting + history | No | No | Yes |
| Best for | One-off checks | Deploy watch, debugging | Production monitoring |
Practical split: SaaS for always-on alerts. Terminal CLI for deploy night and incident debugging. curl for the fastest single-request smoke test.
Programmatic usage in Node.js
Embed monitoring in scripts, cron jobs, or custom tooling:
import { MonitorManager } from 'upstatus'
const manager = new MonitorManager()
manager.add({
url: 'https://api.example.com',
interval: 30,
method: 'POST',
body: JSON.stringify({ check: 'deep' }),
headers: { Authorization: 'Bearer token' },
degradedThreshold: 1500,
expectedStatus: [200, 201],
timeout: 10000,
maxRetries: 2,
})
manager.startAll()
const stats = manager.getMonitor('api.example.com')?.getStats()
console.log(stats)
manager.stopAll()
Export utilities for reports:
import { exportToJson, exportToCsv } from 'upstatus'
const stats = [...manager.monitors.values()].map((monitor) => monitor.getStats())
const json = exportToJson(stats)
Full config options: upstatus README.
FAQ
How is this different from Uptime Kuma?
Uptime Kuma is a self-hosted dashboard with alerting, status pages, and history. upstatus is a terminal CLI with no UI to maintain. Use Kuma for team-wide 24/7 monitoring. Use upstatus when you want to watch endpoints from a shell session in 30 seconds.
Can upstatus replace UptimeRobot?
No. upstatus does not send email/SMS alerts or store months of history. It fills the gap when you need local, immediate visibility without creating another account.
Does it work on Windows?
Yes. Node.js 18+ on Windows, macOS, and Linux. Global install: npm install -g upstatus.
What Node version do I need?
Node 18 or later (uses native fetch).
How do I monitor an endpoint that requires authentication?
Use the programmatic API with headers: { Authorization: 'Bearer ...' }. Do not pass secrets on the CLI in shared screen recordings or shell history. Use environment variables in scripts.
Why does --json not exit after one check?
The CLI runs until you stop it. That is intentional for live monitoring. For CI smoke tests, call Monitor.check() once and process.exit(1) on failure.
What is the default check interval?
30 seconds. Override with -i 60 for 60 seconds.
Try it
npx upstatus https://api.github.com
If you maintain a side project API and only need to know "is it up right now?", a terminal monitor beats opening three browser tabs.
About upstatus
upstatus is an open-source CLI uptime monitor (~15KB gzipped). POST/PUT/PATCH support, degraded thresholds, retry with exponential backoff, JSON/CSV export, and a programmatic API for Node.js and TypeScript.
MIT licensed. Built with TypeScript and logfx for terminal output.
Full feature list and CLI reference: upstatus README.
Top comments (0)