JSON API monitoring with real payload assertions
Updated Aug 19, 2026
APIs lie politely. A health endpoint returning HTTP 200 with {"status":"degraded"} in the body is telling you something is wrong in the only way it knows how, and a status-code monitor will file that under "fine". A JSON value monitor reads the payload and judges it.
The 200 with bad news inside
Well-behaved APIs put operational truth in the body. A health endpoint enumerates its dependencies and marks one unhealthy while still answering 200, because the API itself is up; it is the database replica that is behind. A queue endpoint reports a depth of 40,000. A payments integration reports its circuit breaker open.
None of that changes the status code, and none of it should. The endpoint is working exactly as designed. What has to change is what your monitor looks at.
Dot-notation path assertions
A JSON value monitor takes two settings beyond the usual HTTP ones: a path and an expected value. The probe requests your endpoint, checks the status code, then decodes the body as JSON and reads the value at your path.
Paths use dot notation. A top-level field is just its name; nested fields walk down through objects, like response.status or checks.database.state; array elements are addressed by numeric index, so items.0.state reads the first element. No query language, no filters, no wildcards.
{
"status": "ok",
"response": {
"status": "healthy",
"region": "nyc"
},
"items": [
{ "name": "database", "state": "up" },
{ "name": "redis", "state": "up" }
]
}
Against that body, status resolves to "ok", response.status to "healthy", and items.0.state to "up". Comparison is done as strings, which is what makes the form usable: numbers, booleans, and null are rendered to their JSON literal before comparing. You type what you would see in the payload.
When the assertion fails, you get told why
Three distinct failure messages come out of this probe, and the difference between them is diagnostic on its own:
- Response body is not a JSON object or array. Something answered, but not with JSON: usually an HTML error page, a proxy interstitial, or a login redirect.
- JSON path "checks.database.state" not found. The endpoint returned JSON, but the shape changed: a version bump, a renamed field, or a partial response.
- JSON path "response.status" was "degraded", expected "healthy". The shape is intact and the value is wrong. This is the one you built the monitor for.
One deliberate subtlety: a path that exists and holds null is distinguished from a path that is absent, so "present and null" reports as a mismatch rather than a missing path. A path resolving to an object or array reports that it is not a value, instead of silently comparing a stringified structure.
Any method, any body, stored encrypted
Plenty of API health signals are not behind a GET. A JSON monitor can use GET, HEAD, POST, PUT, PATCH, or DELETE, and the write methods can carry a request body sent verbatim on every check. So you can post a search query and assert the result count, or hit a diagnostic endpoint that only answers to POST.
POST https://api.example.com/v1/health
Content-Type: application/json
{ "include": ["database", "queue", "cache"] }
JSON path: checks.queue.state
Expected value: ok
Headers and body are both encrypted at rest, which matters because this is precisely where bearer tokens end up, and headers are dropped if a redirect leaves the original origin so a monitored endpoint cannot bounce your token to a third party. Configure a HEAD request alongside a path assertion and the probe upgrades it to GET, since HEAD returns no body to inspect.
Setting up a JSON monitor
- Create a monitor and choose the JSON value type.
- Enter the endpoint URL and pick the HTTP method, adding a request body where the method takes one.
- Add request headers for authentication and content type. They are stored encrypted.
- Enter the JSON path. Path and expected value are both required, because a JSON monitor without an assertion is a status-code check wearing a costume.
- Enter the expected value exactly as it appears in the payload.
- Pick an interval, five minutes on the free plan and one minute on Pro, and attach the channels that should hear about it.
These run on the free plan like any other monitor, inside its permanent allowance of ten monitors, one status page, and 30 days of history. Alerting covers email, ntfy, Telegram, Slack, Discord, and generic webhooks on every plan, with exactly-once delivery of each transition to each channel, so an API oscillating between healthy and degraded cannot spam you. Pro is a single flat price per team, no per-seat and no per-monitor fees: 50 monitors, one-minute checks, a year of history, unlimited status pages and members, and custom domains, after 14 days of Pro with no card.
Questions people ask
What path syntax is supported?
Dot notation with numeric indexes for arrays: status, response.status, checks.database.state, items.0.state. No wildcards or expressions. If the value you need is behind logic, expose it as a field on a small health endpoint and check that.
Can I assert on more than one field?
One assertion per monitor. Create a monitor per field, all pointing at the same endpoint, with a shared tag so the dashboard groups them. The alert then names the specific failing field.
Can I monitor a GraphQL API?
Yes. Use POST, put the query document in the request body with a JSON content type, and assert on a path inside the data object. GraphQL famously answers 200 even for errors, which is exactly the case this monitor type exists to catch.
Related reading
Monitoring that watches from the outside
Free plan with 10 monitors, plus a 14-day Pro trial. No credit card required.
Start free