· by Nikola “Golux” Golubovic· api-monitoring· rest-api· oauth2· monitoring

    Monitoring REST APIs: Assertions, Auth and Schedules

    Learn API monitoring best practices: status and JSON body assertions, API key and OAuth2 auth, chained requests, and check scheduling.

    Cover image for Monitoring REST APIs: Assertions, Auth and Schedules

    Modern APIs fail in subtle ways: a 200 response with an empty payload, a token that silently expires, or a downstream dependency that adds 2 seconds of latency without ever returning an error. Basic ping checks miss all of this. Effective API monitoring means asserting on status, latency, and response body - while handling auth and multi-step flows correctly.

    Why Simple Uptime Checks Aren't Enough for APIs

    A GET /health returning 200 OK tells you the process is alive, not that your API works. Real API monitoring needs to validate:

    • HTTP status code (not just "2xx")
    • Response latency against a threshold
    • JSON body structure and specific field values
    • Headers (rate limit remaining, cache status, content-type)

    If you only check for a 2xx status, you'll miss cases where an API returns 200 with {"error": "internal"} in the body - a common anti-pattern in REST services that don't map errors to proper status codes.

    Status and Latency Assertions

    Start with the basics, then layer in body assertions:

    curl -s -o /tmp/resp.json -w "%{http_code} %{time_total}\n" \
      https://api.example.com/v1/orders/123
    

    A monitoring check should fail the run if:

    • Status code is not in the expected set (e.g. 200)
    • Latency exceeds a threshold (e.g. p95 > 800ms)
    • Body fails a JSON assertion

    Asserting on JSON Response Bodies

    Use jq to validate structure and values before wiring the same logic into a monitoring platform:

    curl -s https://api.example.com/v1/orders/123 | jq -e '
      .status == "confirmed" and
      (.total | type == "number") and
      (.items | length > 0)
    '
    

    jq -e sets a non-zero exit code when the expression evaluates to false or null, which makes it trivial to wire into CI or a synthetic check runner. Typical assertions worth automating:

    • Required fields exist (.id, .status, .created_at)
    • Enum fields have expected values (.status in a known set)
    • Arrays are non-empty when they shouldn't be
    • Numeric fields fall within sane bounds (no negative prices, no impossible timestamps)

    Handling Authentication

    Most production APIs are not anonymous. Your monitoring setup needs to handle at least two common patterns.

    API Key Auth

    curl -s -H "Authorization: Bearer $API_KEY" \
      https://api.example.com/v1/accounts/me
    

    Store the key as a secret in your monitoring tool, never inline in the check definition, and rotate it on the same schedule as your other production credentials.

    OAuth2 Client Credentials

    Token-based auth requires a chained request: fetch a token, then use it.

    TOKEN=$(curl -s -X POST https://auth.example.com/oauth/token \
      -d grant_type=client_credentials \
      -d client_id="$CLIENT_ID" \
      -d client_secret="$CLIENT_SECRET" | jq -r '.access_token')
    
    curl -s -H "Authorization: Bearer $TOKEN" \
      https://api.example.com/v1/accounts/me
    

    Tokens expire, so a monitoring suite should cache the token for its lifetime and re-fetch on expiry or on a 401, rather than requesting a new token on every check run.

    Chained Requests and Environments

    Real user flows span multiple calls: log in, create a resource, fetch it, delete it. A monitoring suite that models this looks like:

    1. POST /sessions → capture session_id
    2. POST /orders using session_id → capture order_id
    3. GET /orders/{order_id} → assert status is confirmed
    4. DELETE /orders/{order_id} → assert 204

    Each step's output becomes an input variable for the next. Keep environment-specific values (base URLs, credentials, expected values) out of the check logic itself:

    VariableStagingProduction
    BASE_URLhttps://staging-api.example.comhttps://api.example.com
    CLIENT_IDstaging keyproduction key
    EXPECTED_LATENCY_MS1500500

    This lets the same suite run against multiple environments without duplicating check definitions.

    Scheduling and Alert Fatigue

    Not every endpoint needs the same check frequency. A useful default:

    • Critical write paths (checkout, payments): every 1 minute
    • Read-heavy public endpoints: every 5 minutes
    • Internal/batch endpoints: every 15–30 minutes

    Combine this with retry logic before paging - a single failed run is often a transient network blip. Require 2 consecutive failures from different regions before triggering an incident, and route the alert to on-call rather than a shared channel nobody watches.

    Building This Without Reinventing It

    You can script all of the above with curl and jq in a cron job, but you'll eventually need to solve: secret storage, multi-region execution, historical latency trends, and alerting/escalation. Platforms like ServerInspector run these assertions on a schedule from multiple regions, handle token refresh and chained requests natively, and alert only after failures are confirmed from more than one location - which removes most of the operational burden of hand-rolled scripts.

    Checklist: API Monitoring Coverage

    • Status code assertion on every check
    • Latency threshold per endpoint, not a global default
    • JSON body assertions for required fields and value ranges
    • API key or OAuth2 token handling with secure secret storage
    • Token refresh logic that doesn't hammer the auth endpoint
    • Chained requests for multi-step business flows
    • Separate environments (staging vs production) without duplicated logic
    • Check frequency matched to endpoint criticality
    • Multi-region confirmation before alerting

    FAQ

    How often should I run API monitoring checks?

    For critical write endpoints (checkout, auth, payments), run checks every 1–5 minutes. Lower-traffic or internal endpoints can run every 15–30 minutes. Match frequency to the cost of an undetected outage, not to what's technically cheapest to run.

    Should I assert on response body content or just status codes?

    Both. Status codes catch transport and server-level failures; body assertions catch logic errors where the server returns 200 with invalid or unexpected data. Relying on status alone misses a large class of real production bugs.

    How do I monitor an OAuth2-protected API without hardcoding secrets?

    Store client ID and secret in your monitoring tool's secret manager, fetch a fresh token via the client credentials flow, cache it for its lifetime, and re-fetch only on expiry or a 401 response - never on every single check run.

    What's the right way to avoid alert fatigue on flaky endpoints?

    Require confirmation from at least two consecutive check runs, ideally from different regions, before triggering a page. Combine this with sane latency thresholds set per endpoint rather than one global value.

    Can I monitor internal/private APIs that aren't publicly reachable?

    Yes, as long as your monitoring tool supports checks from an agent inside your network or VPC, or you expose the endpoint through an authenticated gateway reachable from the monitoring provider's regions.