API Monitoring with Checkly: HTTP Checks, Assertions, and Alerts

API Monitoring with Checkly: HTTP Checks, Assertions, and Alerts

Checkly's API checks are HTTP monitors with assertions. You send a request, define what "passing" looks like, and Checkly runs that check from global locations on a schedule. When an assertion fails, you get alerted. Simple premise, a lot of depth in the details.

This guide goes through everything: configuring checks, writing assertions, handling authentication, chaining multi-step checks, and managing environments.

API Check Anatomy

An API check consists of:

  1. Request — method, URL, headers, body
  2. Setup script (optional) — JavaScript that runs before the request
  3. Assertions — what the response must satisfy to pass
  4. Teardown script (optional) — JavaScript that runs after the response

Most checks only need 1 and 3. The scripts unlock advanced patterns like dynamic auth tokens.

Request Configuration

Basic GET

The simplest check:

  • Method: GET
  • URL: https://api.myapp.com/health
  • Assertion: Status code equals 200

This verifies your health endpoint is reachable and returns success. It takes about 5 minutes to set up and catches network-level failures, application crashes, and misrouted traffic.

POST with a JSON Body

For checks that need to create or modify data:

  • Method: POST
  • URL: https://api.myapp.com/v1/items
  • Headers: Content-Type: application/json
  • Body:
{
  "name": "Monitor Test Item",
  "price": 0.01,
  "sku": "MONITOR-TEST-001"
}

Add assertions on status code (201) and the response body (the returned item has an id field).

Headers

Add headers in the Headers section of the check form. Static values go directly in the form. For secrets like API keys, reference environment variables:

Header Value
Authorization Bearer {{API_KEY}}
X-Api-Version 2024-01
Content-Type application/json

The {{VARIABLE_NAME}} syntax references Checkly environment variables. The variable's value is injected at runtime and never appears in the UI.

Assertions

Assertions are where Checkly's API monitoring gets specific. You're not just checking "did the request succeed" — you're defining exactly what a successful response looks like.

Status Code

The most basic assertion. Always add this:

Property Comparison Value
Status code Equals 200

For POST endpoints that create resources, assert 201. For DELETE, assert 204 or 200 depending on your API conventions. Don't skip this — it's the most common failure mode.

Response Time

Response time  |  Less than  |  2000

This asserts the full request completes in under 2 seconds. Set the threshold based on your SLA, not an arbitrary number. If you've committed to p95 under 500ms, assert 500. If you haven't committed to anything, 2000 is a reasonable starting point.

Response time assertions catch degraded performance before users notice slowdowns. A check that passes with a 1900ms response when it usually takes 80ms is a signal worth alerting on.

JSON Body Assertions

This is where API monitoring gets powerful. You can assert on specific fields in the response JSON.

Suppose your GET /api/status returns:

{
  "status": "ok",
  "database": "connected",
  "version": "2.4.1"
}

Add these assertions:

Property Comparison Value
json body $.status Equals ok
json body $.database Equals connected

Now the check passes only if the API is reachable, returns 200, and the JSON confirms both status and database connectivity. An endpoint that returns 200 with {"status": "degraded"} will correctly fail.

For arrays, you can assert on length:

json body $.items.length  |  Greater than  |  0

For nested paths:

json body $.data.user.id  |  Is not null  |

Response Headers

Assert on specific response headers:

Property Comparison Value
Header content-type Contains application/json
Header x-ratelimit-remaining Greater than 0

The rate limit assertion is particularly useful — it tells you if your monitoring itself is consuming your API quota.

Authentication

Static API Keys

Store your API key as a Checkly environment variable (mark as secret), then reference it in the request header:

Authorization: Bearer {{API_KEY}}

At the account level: Account Settings → Environment Variables → + Add Variable.

For production vs staging keys, use Group-level variables. Create a group for production checks, another for staging checks, each with their own API_KEY variable pointing to the appropriate key.

Dynamic Tokens (OAuth, JWT)

When your API requires a fresh JWT or OAuth token, use a Setup Script to fetch the token before the main request runs.

In the check's Setup tab:

// Fetch a fresh auth token before each check run
const response = await fetch('https://auth.myapp.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'client_credentials',
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
  }),
});

const data = await response.json();

// Make the token available to the request via an environment variable
process.env.ACCESS_TOKEN = data.access_token;

Then in your request headers:

Authorization: Bearer {{ACCESS_TOKEN}}

The setup script runs before every check execution. The token is fresh for every run. This handles token expiry correctly — no stale credential failures.

Basic Auth

For APIs using HTTP Basic Auth, either:

  1. Include credentials in the URL: https://user:password@api.myapp.com/endpoint
  2. Add an Authorization header with a base64-encoded value: Basic {{BASE64_CREDENTIALS}}

Option 2 is cleaner — store the base64-encoded credentials as a secret variable.

Multi-Step API Checks

Sometimes you need to make multiple requests in sequence — create a resource, then fetch it, then delete it. This is where Setup Scripts and Teardown Scripts combine.

However, Checkly's API checks don't natively chain multiple HTTP requests in the UI. For multi-step scenarios, you have two options:

Option 1: Browser check with Playwright

Use Playwright's request context for pure HTTP chaining without a browser:

const { chromium } = require('playwright');

const request = await (await chromium.launch()).newContext().request;

// Step 1: Create an item
const createResponse = await request.post('https://api.myapp.com/items', {
  data: { name: 'Test Item', price: 9.99 }
});
const item = await createResponse.json();
console.log('Created item:', item.id);

// Step 2: Fetch the created item
const getResponse = await request.get(`https://api.myapp.com/items/${item.id}`);
if (!getResponse.ok()) {
  throw new Error(`Failed to fetch item ${item.id}: ${getResponse.status()}`);
}

// Step 3: Delete the item (cleanup)
await request.delete(`https://api.myapp.com/items/${item.id}`);
console.log('Cleanup complete');

Option 2: Setup Script for the first call

Use the setup script to make the first request and extract what you need:

// Setup: Create a temporary resource and store its ID
const response = await fetch('https://api.myapp.com/sessions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.API_KEY}` },
  body: JSON.stringify({ userId: 'test-user-123' })
});
const session = await response.json();
process.env.SESSION_ID = session.id;

Then the main request hits /sessions/{{SESSION_ID}} to verify the session is retrievable, and a teardown script deletes it.

Environment Management

Account-Level Variables

Shared across all checks. Use for:

  • API keys that apply everywhere
  • Common test data (shared user IDs, product SKUs)
  • Timeouts and constants

Group-Level Variables

Override account-level variables within a group. Use for:

  • BASE_URL (production vs staging)
  • Environment-specific API keys
  • Feature flags per environment

Create two groups: Production Checks and Staging Checks. Give each a BASE_URL variable. Your check URLs become {{BASE_URL}}/api/products. When you add a new check, put it in the right group and it automatically targets the right environment.

Local Overrides for Testing

When running checks locally via the Checkly CLI, you can override environment variables:

npx checkly test --env-file .env.local

This lets you test against localhost during development without changing the live configuration.

Alert Configuration for API Checks

For production API checks, configure alert channels with:

Alert on failure: immediately (no threshold delay for critical endpoints)
Alert on degraded: when response time exceeds your threshold
Alert on recovery: always — you need to know when incidents resolve

For high-traffic endpoints where brief blips are normal, add a retry (one retry from a different location before alerting). This eliminates false positives from transient network issues without meaningfully increasing mean time to alert.

Check Frequency Guidelines

Endpoint Type Recommended Frequency
Health / status endpoint 1 minute
Authentication (login, token) 5 minutes
Core business flows (checkout, create order) 5 minutes
Secondary endpoints (user profile, search) 10 minutes
Background or batch endpoints 15-30 minutes

Don't set everything to 1 minute. More frequency = more check runs = higher cost, and for most endpoints a 5-minute detection window is acceptable.

Debugging Failed Checks

When a check fails, Checkly shows you:

  1. Which location(s) failed — regional vs global outage
  2. Full request details — exactly what was sent
  3. Full response — status code, headers, body (or error if no response)
  4. Which assertion failed — with the expected vs actual value

Common failure patterns:

  • All locations fail simultaneously → your API is down or returning errors globally
  • One location fails, others pass → DNS, routing, or CDN issue in that region
  • Check passes immediately but fails on retry → race condition or intermittent error
  • Response time assertion fails → performance degradation, investigate slow queries or resource contention
  • JSON body assertion fails → API returned 200 but with unexpected data — a logic bug, not a crash

The most useful debugging step is always: look at the actual response body. If your API is returning a 200 with an error message embedded in JSON (an unfortunately common pattern), the response body tells you that immediately.

Read more

Start now free