Blog
An API can return 200 and still be broken. Define what up means, build a health endpoint that tests dependencies, handle auth and rate limits, and watch third-party APIs.
To monitor API endpoint uptime properly, check three things on every request: the status code, the response time against a fixed budget, and the response body for a value that proves the API actually worked. Point those checks at a health endpoint that tests your real dependencies, run them from several regions with a dedicated read-only credential, and monitor the third-party APIs you depend on as separate checks with their own alert routing.
The rest of this guide covers each of those pieces with commands, example code and thresholds you can copy.
For a website, a 200 response is often good enough. For an API it is not. APIs fail in ways that still produce a clean HTTP response, and a check that only looks at the status line will report them as healthy.
Typical examples:
200 OK with a body like {"error": "database unavailable"}, because a framework error handler swallowed the exception.200 OK with an HTML maintenance page served by a proxy or CDN instead of JSON.200 OK with an empty list, because a cache returned stale or empty data after a failed refresh.So define "up" as three conditions that must all be true. An API endpoint is up only when it returns the expected status code, within your latency budget, with a response body that proves the work was done. Anything less is a partial signal.
You can test all three from a shell before configuring any tool:
curl -sS --max-time 10 \
-o /tmp/body.json \
-w "%{http_code} %{time_total}s\n" \
https://api.example.com/v1/health
jq -e '.status == "ok"' /tmp/body.jsonThe first command prints the status code and total time. The second exits non-zero if the body does not contain the expected value. If you want to separate server time from network time, %{time_starttransfer} gives you something close to TTFB and the other monitoring terms you will see in dashboards.
GET /v1/projects. Avoid endpoints that write data.A health endpoint that only returns {"status":"ok"} without touching anything proves that the process is running and the router works. That is useful, but it misses the most common API outages: the database is unreachable, the connection pool is exhausted, the cache is down.
The usual pattern is two endpoints:
/health/live checks only that the process responds. Container orchestrators use this to decide whether to restart the process. It must never depend on external systems, or a database blip restarts every instance at once./health/ready checks the dependencies the API needs to serve requests. This is the one your external uptime checks should call.A readiness check should run a cheap query against each critical dependency, each with its own short timeout, and return 503 when a critical dependency fails. A sketch in Node.js:
const withTimeout = (p, ms) =>
Promise.race([p, new Promise((_, rej) =>
setTimeout(() => rej(new Error('timeout')), ms))]);
app.get('/health/ready', async (req, res) => {
const [db, cache] = await Promise.allSettled([
withTimeout(pool.query('SELECT 1'), 2000),
withTimeout(redis.ping(), 1000),
]);
const checks = {
database: db.status === 'fulfilled' ? 'ok' : 'fail',
cache: cache.status === 'fulfilled' ? 'ok' : 'degraded',
};
const healthy = checks.database === 'ok';
res.status(healthy ? 200 : 503).json({
status: healthy ? 'ok' : 'fail',
checks,
});
});A few rules keep this endpoint honest without turning it into a liability:
degraded with a 200, not a 503.The health endpoint is not the whole picture. It runs your code path for health, not the code path customers use. That is why the step list above also includes one or two real read endpoints with body assertions.
Most real endpoints require authentication, so the monitor needs a credential. Treat it like any other production secret.
uptime-monitor, so its requests are easy to filter in logs and analytics.Authorization: Bearer ..., not in the query string, where it ends up in access logs.401.That last point matters for alerting. A 401 or 403 from your monitor almost always means the credential broke, not the API. If your tool lets you, route auth failures to the team channel rather than paging, and fix them during working hours.
Be careful with IP allowlists. Monitoring probes run from many locations and their addresses can change, so allowlisting probe IPs tends to break silently. A scoped token is easier to maintain.
Monitoring traffic is small but constant, and it adds up. A check every 60 seconds from 3 regions on 4 endpoints is 12 requests per minute, 720 per hour, every hour. If the monitoring key shares a rate limit bucket with a customer or with your own integration tests, you will eventually see 429 Too Many Requests in your checks.
Three ways to handle it:
/health/* from rate limiting entirely, or give it a generous separate limit.Then decide what a 429 means. If your monitor hits the limit, it is noise. If customers hit it, it is an outage from their point of view. Keeping the monitor out of the customer limit lets you treat any 429 it sees as a real signal.
Your API can be fully up while a dependency you do not control is failing: a payment provider, an email API, an auth provider, a geocoding service. Customers experience that as your outage.
Monitor these as separate checks:
The value is speed of diagnosis. When your own API starts throwing errors, a red check on the payment provider tells you in seconds where to look, and it tells your support team what to say. The same logic applies to your own infrastructure in between: errors on the proxy layer often look like API failures, and the guide on 502 Bad Gateway causes between proxy and upstream covers how to tell them apart.
Adjust these to your own traffic, but they are a sensible default for a typical JSON API:
If you still get alerts that turn out to be nothing, the article on reducing false positive alerts with retries and realistic timeouts goes deeper into tuning.
m0nitor is one option for running these checks. Its HTTP(S) and other monitor types run on a fixed interval from distributed probes in several regions, and an outage must be confirmed by multiple locations before an alert fires. You can point an HTTP(S) monitor at your readiness endpoint, add monitors for the third-party APIs you depend on, and use TCP port or DNS monitors for the layers underneath. With alert workflows you can send different monitor events to different channels, open an incident or call a webhook, which fits the split between paging for your own API and notifying a channel for dependencies.
No. APIs regularly return 200 with an error message, an HTML page from a proxy or empty data. Combine the status code with a timeout and a body assertion, such as checking that a JSON field equals an expected value.
Yes, the readiness endpoint that external monitors call should run a cheap query like SELECT 1 with a short timeout and return 503 if it fails. Keep a separate liveness endpoint without dependencies for container restarts.
Every 60 seconds is a common default for a health endpoint. Heavier business endpoints and third-party APIs can run every 1 to 5 minutes to limit load and rate limit usage while still catching outages quickly.
For real business endpoints, yes. Use a dedicated read-only token scoped to a test tenant, send it in a header, and track its expiry. Keep the health endpoint unauthenticated but strip it of anything sensitive.