api monitoring

How to monitor a JSON API endpoint (not just "is it 200?")

An API endpoint returning HTTP 200 doesn't mean the API is working. Maybe it's returning 200 with an error body. Maybe the response shape changed. Maybe it's slow but technically successful. Here's how to monitor APIs in a way that catches the failures HTTP status codes miss.

MyUptimeBot Team · July 7, 2026 · 7 min read · For developers

The problem with just checking the status code

The simplest API monitoring is “GET the endpoint, alert if not 200.” This catches:

  • The API is completely down
  • The endpoint returns a 4xx or 5xx error
  • The network can’t reach the endpoint

It misses:

  • The endpoint returns 200 with a JSON error body ({"error": "rate limited"})
  • The schema changed and a field your consumers depend on is gone
  • The response is technically valid but contains stale or wrong data
  • The response is correct but takes 30 seconds to return
  • The endpoint is returning a cached error from a CDN

In practice, “API is working” is a much stronger claim than “API returns 200.” Useful API monitoring exercises the actual contract, not just the connection.

What to check, in order of value

The escalating checks, from cheapest to most useful:

1. HTTP status code. Free, fast, catches the obvious. Always include.

2. Response time. A 200 that takes 8 seconds is functionally broken for most consumers. Set a reasonable threshold (300ms? 1s? depends on the endpoint) and alert on degradation.

3. Response content-type. If you’re expecting application/json and you get text/html, your CDN is returning an error page where the API used to be. The status might be 200, but the response is wrong.

4. JSON shape validation. Parse the response and check that the expected fields exist. If a critical field disappears, alert before consumers notice.

5. JSON value validation. Check that values are in expected ranges. A health endpoint that returns {"status": "ok"} is fine; one that returns {"status": "degraded"} is not.

6. End-to-end exercise. For critical flows, post a request that exercises the full stack — POST to create a resource, GET to read it back, verify the data round-trips correctly.

Each level catches problems the previous ones miss, at slightly higher cost and complexity.

Tools that do JSON-aware checking

A few approaches, with examples:

Simple shell scripting

response=$(curl -sf -o /tmp/resp.json -w "%{http_code} %{time_total}" https://api.example.com/health)
status_code=$(echo "$response" | awk '{print $1}')
time_total=$(echo "$response" | awk '{print $2}')

if [ "$status_code" -ne 200 ]; then
  echo "ALERT: status code $status_code"
  exit 1
fi

if [ "$(jq -r .status /tmp/resp.json)" != "ok" ]; then
  echo "ALERT: status field not ok"
  exit 1
fi

if [ "$(echo "$time_total > 2.0" | bc)" -eq 1 ]; then
  echo "WARN: response time ${time_total}s"
fi

Run via cron, pipe failures to your alerting webhook. Works fine for a few endpoints; cumbersome at scale.

Hosted monitoring with response body checks

Most uptime monitoring services support content checking — either a substring match (“response must contain "status":"ok"”) or a regex. Less expressive than full JSON parsing but covers most cases.

For more sophisticated checking — actual JSON schema validation — you usually need a service that explicitly supports it (Checkly, Better Stack, AssertibleHQ) or you roll your own.

API-specific tools

For API-heavy stacks, dedicated tools like Checkly, Postman Monitors, or AssertibleHQ offer scripted checks — you write a small JavaScript or YAML config that posts requests, validates responses, and chains them. Useful when your “check” is really a multi-step workflow.

Designing a good health endpoint

If you control the API, the easiest path to good monitoring is designing a health endpoint that surfaces internal state.

A bad health endpoint:

GET /health → 200 OK
{ "status": "ok" }

The “ok” is meaningless. The server returning anything is enough to set status to ok. You’re effectively just checking that the HTTP listener is alive.

A better health endpoint:

GET /health → 200 OK
{
  "status": "ok",
  "checks": {
    "database": { "status": "ok", "latency_ms": 12 },
    "cache":    { "status": "ok", "latency_ms": 3 },
    "queue":    { "status": "ok", "depth": 4 },
    "storage":  { "status": "degraded", "reason": "high latency to s3" }
  },
  "version": "v1.42.3",
  "uptime_seconds": 132450
}

Each subsystem has its own status. A monitor can check checks.database.status and alert if anything is degraded — even when the overall response is 200.

The standard for this is the health check RFC draft (sometimes called RFC Health+JSON), which formalizes this shape. Worth implementing if you have multiple consumers.

The “deep” vs “shallow” health check

There’s a subtle trade-off in health endpoint design:

Shallow check: only verifies the API process is running and can respond. Fast, lightweight, doesn’t exercise dependencies.

Deep check: verifies the API can reach all its critical dependencies — database, cache, third-party APIs. Slower, more expensive, but a true health signal.

For load balancers, you want a shallow check — a load balancer health probe runs every few seconds, and you don’t want to hammer the database with every probe. For monitoring, you usually want a deep check — once a minute is fine, and you want to catch dependency failures.

A common pattern is to expose both:

  • /health — shallow, used by load balancers
  • /health/deep — deep, used by external monitoring

Or to expose query parameters: /health?deep=true.

A useful monitoring pattern: idempotent test endpoints

For more thorough monitoring, expose an internal endpoint that exercises the full request path — possibly hitting a sandboxed version of the production database.

POST /internal/monitoring/echo
Body: {"value": "ping"}
Response: 200, {"echoed": "ping", "round_trip_ms": 42}

The monitor calls this endpoint and verifies the echo matches. This exercises:

  • HTTP routing
  • Authentication/authorization (if you require an auth header)
  • JSON parsing
  • Application logic
  • Database round-trip (if implemented to write/read a record)
  • Response serialization

Protect it from public abuse with an auth token. The monitor knows the token; randos don’t.

Validating JSON schemas

For APIs you don’t control — third-party services you depend on — schema validation matters more. A vendor can technically “stay up” while silently changing their response shape, and your consumers break.

Lightweight approach: define an expected schema and validate against it.

// Using ajv for JSON schema validation
const Ajv = require('ajv');
const ajv = new Ajv();

const schema = {
  type: 'object',
  required: ['user_id', 'email', 'plan'],
  properties: {
    user_id: { type: 'integer' },
    email: { type: 'string', format: 'email' },
    plan: { type: 'string', enum: ['free', 'pro', 'enterprise'] }
  }
};

const validate = ajv.compile(schema);

async function checkApi() {
  const res = await fetch('https://api.example.com/user/me', {
    headers: { Authorization: `Bearer ${TOKEN}` }
  });
  const data = await res.json();

  if (!validate(data)) {
    console.error('Schema validation failed:', validate.errors);
    return false;
  }
  return true;
}

If a third-party API silently adds a new required field or drops one, you find out from your monitor — not from your customers an hour later.

Edge cases

Auth tokens for monitor requests. Health endpoints should generally not require auth (defeats the point). For deeper monitoring that does require auth, generate a long-lived monitoring-specific token and rotate it on a schedule. Don’t reuse a real user token.

Rate limits. Some APIs have aggressive rate limits. Monitoring an external API at 1 check per minute = 1,440 calls a day, which can exceed limits. Configure check frequency appropriately or use a service that respects rate limits.

Caching. A monitor that checks the same URL every minute may hit a CDN cache instead of the origin. Append a cachebuster (?_=$(date +%s)) or use a path that bypasses caching.

False positives during deploys. During a deploy, an endpoint might briefly return 5xx. Most monitoring services handle this with re-check logic, but for self-built monitors you may want to debounce — require 2+ consecutive failures before alerting.

What MyUptimeBot does for API monitoring

We support content keyword checking (substring match on the response body), which covers most common cases — “make sure the response contains \"status\":\"ok\"”. Full JSON schema validation is on our roadmap but not shipped yet.

For more sophisticated API monitoring (scripted multi-step checks, JSON schema enforcement, JavaScript assertions), services like Checkly are purpose-built for that and worth the cost if API contracts are critical to your business.

For most teams the answer is layered monitoring: basic uptime + keyword checks from a service like us, plus scripted API checks from a dedicated tool for the most critical endpoints. Different tools for different jobs.

The principle

The best API monitor is one that fails the same way your customers’ code would fail. If your API consumers check response.data.user_id, your monitor should too. If they parse JSON, your monitor should. If they care about response time, your monitor should alert on slowness, not just outages.

The closer your monitoring resembles real consumer usage, the more accurately it reflects whether the API is actually working. HTTP 200 is the bare minimum. Everything above that is what separates “alerting when the network dies” from “alerting when consumers would notice.”

MyUptimeBot
Watching the internet

We build a friendly bot that watches your websites every 30 seconds and alerts you the moment something breaks. Notes here come from running that infrastructure, talking to the people who depend on it, and reading the postmortems no one publishes.

Stop finding out from customers.

One monitor, free forever. A friendly bot doing the worrying for you.