DEV Community

Cover image for How to Monitor API Uptime From the Command Line (No SaaS Required)
Chintan Shah
Chintan Shah

Posted on • Edited on

How to Monitor API Uptime From the Command Line (No SaaS Required)

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
Enter fullscreen mode Exit fullscreen mode

-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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

No install? Use npx:

npx upstatus https://api.github.com
Enter fullscreen mode Exit fullscreen mode

Monitor one or more URLs

upstatus https://api.example.com
Enter fullscreen mode Exit fullscreen mode

Multiple endpoints, default 30-second interval:

upstatus https://api.example.com https://status.example.com https://api.github.com
Enter fullscreen mode Exit fullscreen mode

Custom interval (60 seconds):

upstatus https://api.example.com -i 60
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"}'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

CSV works too:

upstatus https://api.example.com --export csv -o uptime-report.csv
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add to GitHub Actions:

- name: Smoke test API
  run: curl -sf https://api.example.com/health
Enter fullscreen mode Exit fullscreen mode

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`)
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Export utilities for reports:

import { exportToJson, exportToCsv } from 'upstatus'

const stats = [...manager.monitors.values()].map((monitor) => monitor.getStats())
const json = exportToJson(stats)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)