Multi-Step Synthetic Checks

Define multi-step API workflows that authenticate, extract values, and validate responses across a sequence of HTTP requests.

What is synthetic monitoring?

Synthetic monitoring simulates real user workflows by executing a sequence of HTTP requests. Instead of checking a single endpoint, you can model an entire flow — authenticate, create a resource, verify it, then clean up — and validate each step along the way.

Creating a synthetic monitor

  1. Navigate to Monitors > New Monitor and select Synthetic as the type.
  2. Enter a name and set the check interval.
  3. Define your steps as a JSON array in the Steps editor.

Step definition

Each step is a JSON object with the following fields:

{
  "name": "Authenticate",
  "url": "https://api.example.com/auth/login",
  "method": "POST",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": "{\"email\": \"test@example.com\", \"password\": \"testpass\"}",
  "expected_status": 200
}

Required fields

  • name — A descriptive label for the step, shown in results.
  • url — The endpoint to call.
  • method — HTTP method (GET, POST, PUT, PATCH, DELETE).
  • expected_status — The expected HTTP status code. The step fails if the actual status doesn’t match.

Optional fields

  • headers — Key-value pairs of HTTP headers.
  • body — Request body as a string (typically JSON).
  • extract_json — Extract values from the response for use in later steps.
  • assert_body — Validate that the response body contains a specific string.

Extracting values between steps

Use extract_json to pull values from a response and reference them in subsequent steps with {{variable}} syntax. The extractor uses dot notation to traverse JSON:

[
  {
    "name": "Login",
    "url": "https://api.example.com/auth/login",
    "method": "POST",
    "headers": { "Content-Type": "application/json" },
    "body": "{\"email\": \"test@example.com\", \"password\": \"testpass\"}",
    "expected_status": 200,
    "extract_json": {
      "auth_token": "token",
      "user_id": "user.id"
    }
  },
  {
    "name": "Get user profile",
    "url": "https://api.example.com/users/{{user_id}}",
    "method": "GET",
    "headers": {
      "Authorization": "Bearer {{auth_token}}"
    },
    "expected_status": 200,
    "assert_body": "test@example.com"
  }
]

In this example, the first step logs in and extracts token (from the response root) and user.id (nested under the user object). The second step uses both values in the URL and headers.

Dot notation examples

Given a response:

{
  "token": "abc123",
  "user": {
    "id": "usr_456",
    "profile": {
      "name": "Test User"
    }
  },
  "items": [{ "id": "item_1" }]
}
  • token extracts "abc123"
  • user.id extracts "usr_456"
  • user.profile.name extracts "Test User"

Asserting response content

Use assert_body to verify the response body contains a specific string. This is a simple substring match — if the string is not found in the response, the step fails.

{
  "name": "Verify dashboard loads",
  "url": "https://app.example.com/dashboard",
  "method": "GET",
  "expected_status": 200,
  "assert_body": "Welcome back"
}

Execution and results

Steps execute sequentially. If any step fails (wrong status code, missing assertion, network error), the entire synthetic check is marked as failed and subsequent steps are skipped.

Results show each step’s outcome: status code, response time, extracted values, and any assertion failures. This makes it easy to pinpoint exactly where a workflow breaks.

Next steps