Skip to content
Go back

Validating JSON API Responses in k6

Edit page

When you’re load-testing an API, it’s not enough to hit the endpoint and measure speed. You also need to validate the data inside the JSON response to confirm the API behaves correctly under load.

k6 gives you a simple but powerful way to validate JSON using checks.

Table of contents

Open Table of contents

Example API Response (Product List)

Imagine your API returns this:

[
  {
    "id": 101,
    "name": "Wireless Mouse",
    "price": 899,
    "inStock": true,
    "tags": ["electronics", "accessories"],
    "rating": {
      "average": 4.5,
      "count": 132
    }
  }
]

We’ll validate this using k6.


Validate Status Codes

Validate if api has valid response code

check(res, {
  "status is 200": r => r.status === 200,
  "Status is not 500": r => r.status !== 500,
  "Auth failed": r => r.status !== 401,
  "status is 2XX": r => r.status >= 300 && r.status < 400,

});

It checks,

  1. API Response is 200
  2. API Response is not 500 (Server Errors) or 400 (Authencation Errors)
  3. API Response is within 300 to 399 (Useful for moved resources)

Validate Response Time (k6 timings)

k6 tracks detailed timing metrics inside every response.

You can validate timing directly:

check(res, {
  "response under 500ms": r => r.timings.duration < 500,
  "ttfb under 200ms": r => r.timings.waiting < 200
});

You can also inspect individual timing phases:

Timing MetricMeaning
waitingtime waiting for the first byte (TTFB)
durationfull request time
sendingtime to send request body
receivingtime to download response

Validate Content Type

Make sure you’re actually getting JSON:

check(res, {
  "is JSON": r => r.headers["Content-Type"]?.includes("application/json"),
});

A backend returning HTML or text during load is more common than you think.

Validate Response Body and Size

Make sure api response body is not empty.

check(res, {
  "response < 1MB": r => r.body.length > 0,
});

Make sure response is not more than specified size.

check(res, {
  "response < 1MB": r => r.body.length < 1_000_000,
});

This prevents regressions like “suddenly returning 4 MB when it used to return 200 KB.”

Validate Error Responses Under Load

if (res.status !== 200) {
  check(res, {
    "error message exists": r => r.json("message") !== undefined,
  });
}

You can choose size limits based on real-world expectations.

Validate Basic Structure (Shape)

Check that the response is a JSON array and not empty.

check(res, {
  "is array": r => Array.isArray(r.json()),
  "not empty": r => r.json().length > 0,
});

Validate Required Fields

Make sure each product has fields such as id, name, price that must exist.


const product = res.json()[0]; // Fetch first product from product list

check(product, {
  "id exists": p => p.id !== undefined,
  "name exists": p => p.name !== undefined,
  "price exists": p => p.price !== undefined,
});

This prevents breaking changes in API contracts.


Validate Data Types

Confirm that each field has the type your application expects.

check(product, {
  "id is number": p => typeof p.id === "number",
  "name is string": p => typeof p.name === "string",
  "price is number": p => typeof p.price === "number",
  "inStock is boolean": p => typeof p.inStock === "boolean",
  "tags is array": p => Array.isArray(p.tags),
});

If types shift under load (yes, this happens), your test catches it immediately.


Validate Value Ranges (Constraints)

Example rules you might enforce:

check(product, {
  "price is positive": p => p.price >= 0,
  "rating avg valid": p => p.rating.average >= 0 && p.rating.average <= 5,
  "rating count positive": p => p.rating.count >= 0,
});

Validate Field Format (Patterns)

Example: product name should not be empty.

check(product, {
  "name not empty": p => p.name.trim().length > 0,
});

Validate Nested JSON Objects

The rating key is a nested object. We can validate its structure and values too.

check(product.rating, {
  "rating has avg": r => r.average !== undefined,
  "rating has count": r => r.count !== undefined,
});

Nested checks prevent silent backend bugs.


Validate Arrays

The tags field is an array. You can validate:

check(product, {
  "tags array valid": p =>
    Array.isArray(p.tags) &&
    p.tags.every(t => typeof t === "string"),
});

Validate Pagination Metadata (if applicable)

Example endpoint:

/products?skip=0&limit=20

Typical metadata:

{
  "total": 100,
  "skip": 0,
  "limit": 20,
  "data": [ ... ]
}

Checks:

check(res.json(), {
  "valid limit": j => j.limit > 0,
  "valid skip": j => j.skip >= 0,
  "valid data size": j => j.data.length <= j.limit,
});

Putting It All Together — Full k6 Script Snippet

import http from "k6/http";
import { check } from "k6";

export const options = {
  vus: 1,
  duration: "10s",
};

export default function () {
  const res = http.get("https://example.com/api/products");

  // Basic response checks
  check(res, {
    "status 200": r => r.status === 200,
    "is JSON": r => r.headers["Content-Type"]?.includes("application/json"),
    "duration < 500ms": r => r.timings.duration < 500,
    "response < 1MB": r => r.body.length < 1_000_000,
  });

  const products = res.json();
  if (!Array.isArray(products) || !products.length) return;

  const p = products[0];

  // Validate fields and types
  check(p, {
    "id number": p => typeof p.id === "number",
    "name string": p => typeof p.name === "string",
    "price number": p => typeof p.price === "number",
    "inStock boolean": p => typeof p.inStock === "boolean",
    "tags array": p => Array.isArray(p.tags),
  });

  // Value ranges
  check(p, {
    "price >= 0": p => p.price >= 0,
    "rating valid": p =>
      p.rating.average >= 0 &&
      p.rating.average <= 5 &&
      p.rating.count >= 0,
  });

  // Array checks
  check(p.tags, {
    "tags valid": t => t.every(x => typeof x === "string"),
  });
}
k6-course/json-test.js

Summary — What You Can Validate in JSON With k6

Here’s the complete checklist you now know how to validate:

✓ Structure (object/array)
✓ Required fields
✓ Data types
✓ Value ranges (min/max)
✓ Formats (regex)
✓ Nested objects
✓ Arrays (length, types)
✓ Relationships between fields
✓ Pagination metadata
✓ Business rules
✓ Security-sensitive values

This demonstrates almost every validation technique in one place. This way we can ensures our load tests protect not just performance, but correctness and data integrity.


Edit page
Share this post on:

Next Post
Introduction & Getting Started with k6