Postman: API & Performance Testing

A comprehensive guide to API testing and performance testing with Postman. From building requests to running automated test suites in CI/CD pipelines, learn everything you need to validate, monitor, and load-test your APIs.

Introduction

What is Postman?

Postman is the world's most popular API development and testing platform. Originally created as a simple Chrome extension for sending HTTP requests, it has evolved into a full-featured ecosystem for designing, documenting, testing, and monitoring APIs. Millions of developers and QA engineers rely on Postman to validate API behavior, automate test suites, and ensure API reliability.

Why Use Postman for API Testing?

  • Intuitive GUI — Build and send HTTP requests without writing code. Perfect for exploratory testing and debugging API issues quickly.
  • Powerful scripting — Write JavaScript-based pre-request scripts and test assertions using a built-in sandbox with the pm API.
  • Collections — Organize requests into collections that can be shared, versioned, and executed as automated test suites.
  • Environment management — Switch between development, staging, and production environments with a single click using variable scoping.
  • Data-driven testing — Run the same collection with different data sets using CSV or JSON files, covering edge cases systematically.
  • CI/CD integration — Export collections and run them via Newman (Postman's CLI) in any CI/CD pipeline — GitHub Actions, Jenkins, GitLab CI, etc.

API Testing vs Performance Testing

AspectAPI TestingPerformance Testing
GoalVerify correctness, data integrity, error handlingMeasure speed, throughput, stability under load
ScopeIndividual endpoints, workflows, edge casesSystem-wide behavior under concurrent load
Key metricsStatus codes, response body, schema validationResponse time, throughput (req/s), error rate, P95/P99
Tools in PostmanTest Scripts, Collection Runner, NewmanCollection Runner iterations, Newman parallel, Monitors
When to runEvery commit, PR, deploymentBefore releases, after infrastructure changes
Key TakeawayPostman bridges the gap between manual exploratory testing and fully automated API validation. You can start by manually crafting requests, then progressively add scripts and automation until you have a complete test suite running in CI/CD.

Getting Started

Download and Install

Postman is available as a desktop application for Windows, macOS, and Linux. You can also use the web version at web.postman.co, though the desktop app provides a better experience for local development.

  1. Visit postman.com/downloads and download the app for your OS
  2. Install and launch Postman
  3. Create a free account or sign in (required for syncing and collaboration)
  4. You are ready to send your first request

Workspace Setup

Workspaces are containers that hold your collections, environments, and other Postman resources. Use them to organize your work by project or team.

  • Personal Workspace — Private to you. Great for learning and personal projects.
  • Team Workspace — Shared with your team. All members can view and edit collections.
  • Public Workspace — Visible to anyone. Used for open-source API documentation.

Creating Collections

A collection is a group of related API requests. Think of it as a test suite — it contains requests organized in folders, along with shared scripts, variables, and documentation.

  1. Click New in the sidebar, then select Collection
  2. Name your collection (e.g., "User API Tests")
  3. Add folders to organize by feature (e.g., "Auth", "Users", "Products")
  4. Add requests to each folder
  5. Set collection-level variables and scripts that apply to all requests
TipUse a naming convention for your collections: ProjectName - FeatureArea. For example: "E-Commerce API - User Management", "E-Commerce API - Orders". This makes it easy to find and manage collections as your project grows.

HTTP Requests

The foundation of API testing is sending HTTP requests and inspecting responses. Postman supports all standard HTTP methods and provides a rich interface for configuring headers, query parameters, request bodies, and authentication.

GET Request

GET requests retrieve data from the server. They should never modify server state. Use query parameters to filter, paginate, or search.

GET /api/usersjson
1// GET request to fetch a list of users
2// URL: {{baseUrl}}/api/users?page=1&limit=10
3//
4// Headers:
5//   Content-Type: application/json
6//   Authorization: Bearer {{authToken}}
7//
8// Response (200 OK):
9{
10  "data": [
11    {
12      "id": 1,
13      "name": "John Doe",
14      "email": "john@example.com",
15      "role": "admin"
16    },
17    {
18      "id": 2,
19      "name": "Jane Smith",
20      "email": "jane@example.com",
21      "role": "user"
22    }
23  ],
24  "pagination": {
25    "page": 1,
26    "limit": 10,
27    "total": 42,
28    "pages": 5
29  }
30}

POST Request

POST requests create new resources. The request body contains the data for the new resource, typically sent as JSON.

POST /api/usersjson
1// POST request to create a new user
2// URL: {{baseUrl}}/api/users
3//
4// Headers:
5//   Content-Type: application/json
6//   Authorization: Bearer {{authToken}}
7//
8// Request Body (raw JSON):
9{
10  "name": "Alice Johnson",
11  "email": "alice@example.com",
12  "password": "SecurePass123!",
13  "role": "user",
14  "preferences": {
15    "theme": "dark",
16    "notifications": true,
17    "language": "en"
18  }
19}
20
21// Response (201 Created):
22{
23  "id": 3,
24  "name": "Alice Johnson",
25  "email": "alice@example.com",
26  "role": "user",
27  "createdAt": "2025-01-15T10:30:00Z"
28}

PUT and DELETE Requests

PUT updates an existing resource (full or partial replacement), while DELETE removes it. These complete the CRUD cycle.

PUT & DELETE /api/users/:idjson
1// PUT request to update a user
2// URL: {{baseUrl}}/api/users/3
3//
4// Request Body (raw JSON):
5{
6  "name": "Alice Johnson-Smith",
7  "preferences": {
8    "theme": "light",
9    "notifications": false,
10    "language": "en"
11  }
12}
13
14// Response (200 OK):
15{
16  "id": 3,
17  "name": "Alice Johnson-Smith",
18  "email": "alice@example.com",
19  "role": "user",
20  "updatedAt": "2025-01-15T11:00:00Z"
21}
22
23// -----------------------------------------------
24
25// DELETE request to remove a user
26// URL: {{baseUrl}}/api/users/3
27//
28// Headers:
29//   Authorization: Bearer {{authToken}}
30//
31// Response (204 No Content)
32// Empty response body
HTTP Methods at a Glance
  • GET — Read/retrieve data. Idempotent, no side effects.
  • POST — Create a resource. Not idempotent.
  • PUT — Replace/update a resource. Idempotent.
  • PATCH — Partial update. May or may not be idempotent.
  • DELETE — Remove a resource. Idempotent.

Environment Variables

Environment variables let you switch between different configurations (dev, staging, production) without modifying your requests. Variables are referenced using {{variableName}} syntax in URLs, headers, and bodies.

Setting Up Environments

Environmentsjson
1// Environment: "Development"
2{
3  "baseUrl": "http://localhost:3000",
4  "authToken": "",
5  "testUserEmail": "dev-test@example.com",
6  "testUserPassword": "devpass123",
7  "apiVersion": "v1"
8}
9
10// Environment: "Staging"
11{
12  "baseUrl": "https://staging-api.example.com",
13  "authToken": "",
14  "testUserEmail": "staging-test@example.com",
15  "testUserPassword": "stagingpass456",
16  "apiVersion": "v1"
17}
18
19// Environment: "Production"
20{
21  "baseUrl": "https://api.example.com",
22  "authToken": "",
23  "testUserEmail": "readonly-test@example.com",
24  "testUserPassword": "prodReadOnly789",
25  "apiVersion": "v1"
26}

Variable Scopes

Postman has multiple variable scopes with a clear precedence order. Understanding scopes is essential for managing complex test suites.

Variable Scopesjavascript
1// Setting variables at different scopes in scripts:
2
3// --- Collection variables (shared across all requests in a collection) ---
4pm.collectionVariables.set("apiKey", "abc-123-def-456");
5pm.collectionVariables.get("apiKey");
6
7// --- Environment variables (specific to selected environment) ---
8pm.environment.set("authToken", "eyJhbGciOiJIUzI1NiIs...");
9pm.environment.get("authToken");
10
11// --- Global variables (available across ALL collections) ---
12pm.globals.set("appVersion", "2.5.0");
13pm.globals.get("appVersion");
14
15// --- Local variables (only available in the current request/script) ---
16pm.variables.set("tempValue", "only-for-this-request");
17pm.variables.get("tempValue");
18
19// Variable resolution order (highest to lowest priority):
20// 1. Local (pm.variables)
21// 2. Data (from Collection Runner CSV/JSON)
22// 3. Environment (pm.environment)
23// 4. Collection (pm.collectionVariables)
24// 5. Global (pm.globals)

Dynamic Variables

Postman provides built-in dynamic variables that generate random data on the fly. These are invaluable for creating unique test data without scripts.

Dynamic Variablesjson
1// Postman built-in dynamic variables (use directly in request fields):
2//
3// {{$guid}}          -> "d96d6bba-9e8c-4b7d-b5b7-0fcaa8e3c3a3"
4// {{$timestamp}}     -> 1705312200
5// {{$isoTimestamp}}  -> "2025-01-15T10:30:00.000Z"
6// {{$randomInt}}     -> 742
7// {{$randomUUID}}    -> "6929bb52-3ab2-448a-9796-d6480ecad36b"
8//
9// {{$randomFirstName}}  -> "Alice"
10// {{$randomLastName}}   -> "Johnson"
11// {{$randomEmail}}      -> "alice.johnson@example.com"
12// {{$randomPhoneNumber}} -> "+1-555-0142"
13// {{$randomCity}}       -> "San Francisco"
14// {{$randomCountry}}    -> "United States"
15//
16// {{$randomColor}}   -> "blue"
17// {{$randomBoolean}} -> true
18// {{$randomPrice}}   -> "42.99"
19
20// Using dynamic variables in a POST body:
21{
22  "orderId": "{{$guid}}",
23  "customer": {
24    "firstName": "{{$randomFirstName}}",
25    "lastName": "{{$randomLastName}}",
26    "email": "{{$randomEmail}}"
27  },
28  "amount": "{{$randomPrice}}",
29  "createdAt": "{{$isoTimestamp}}"
30}
WarningNever store real credentials or secrets in Postman environments that are synced to the cloud. Use Current Value (local only) instead of Initial Value (synced) for sensitive data like passwords and API keys.

Pre-request Scripts

Pre-request scripts run before a request is sent. They are written in JavaScript and have access to the full pm API. Common use cases include generating authentication tokens, setting up dynamic data, and configuring request headers.

Token Generation

The most common pre-request script pattern is automatically refreshing an authentication token before each request:

Pre-request Script: Authjavascript
1// Pre-request Script: Generate an auth token before the request runs
2// This script runs BEFORE the main request is sent
3
4const loginUrl = pm.environment.get("baseUrl") + "/api/auth/login";
5
6const loginPayload = {
7    email: pm.environment.get("testUserEmail"),
8    password: pm.environment.get("testUserPassword")
9};
10
11pm.sendRequest({
12    url: loginUrl,
13    method: "POST",
14    header: {
15        "Content-Type": "application/json"
16    },
17    body: {
18        mode: "raw",
19        raw: JSON.stringify(loginPayload)
20    }
21}, function (err, response) {
22    if (err) {
23        console.error("Login failed:", err);
24        return;
25    }
26
27    const jsonData = response.json();
28
29    // Store the token in the environment for subsequent requests
30    pm.environment.set("authToken", jsonData.token);
31    pm.environment.set("refreshToken", jsonData.refreshToken);
32    pm.environment.set("tokenExpiry", jsonData.expiresAt);
33
34    console.log("Auth token refreshed successfully");
35});

Dynamic Data Setup

Generate unique test data, conditional logic, and request modifications:

Pre-request Script: Data Setupjavascript
1// Pre-request Script: Dynamic data generation and setup
2
3// Generate a unique test user for this request
4const timestamp = Date.now();
5const uniqueEmail = `test-user-${timestamp}@example.com`;
6
7pm.environment.set("testEmail", uniqueEmail);
8pm.environment.set("testName", "Test User " + timestamp);
9
10// Generate a random product SKU
11const sku = "SKU-" + Math.random().toString(36).substring(2, 10).toUpperCase();
12pm.collectionVariables.set("productSku", sku);
13
14// Conditional logic: skip token refresh if token is still valid
15const tokenExpiry = pm.environment.get("tokenExpiry");
16const now = Math.floor(Date.now() / 1000);
17
18if (tokenExpiry && parseInt(tokenExpiry) > now + 60) {
19    console.log("Token still valid, skipping refresh");
20} else {
21    console.log("Token expired or missing, refreshing...");
22    // (call pm.sendRequest to refresh the token here)
23}
24
25// Set a request-level timeout header
26pm.request.headers.add({
27    key: "X-Request-ID",
28    value: pm.variables.replaceIn("{{$guid}}")
29});
TipPre-request scripts can also be set at the collection level orfolder level. A collection-level pre-request script runs before every request in the collection, making it the ideal place for shared auth logic.

Test Scripts

Test scripts run after a response is received. This is where you write assertions to validate status codes, response bodies, headers, and response times. Postman uses the Chai assertion library via pm.test() and pm.expect().

Basic Assertions

Tests: Basic Assertionsjavascript
1// Test Scripts: Written in the "Tests" tab, run AFTER the response is received
2
3// --- Basic status code checks ---
4pm.test("Status code is 200", function () {
5    pm.response.to.have.status(200);
6});
7
8pm.test("Status code is in 2xx range", function () {
9    pm.expect(pm.response.code).to.be.within(200, 299);
10});
11
12// --- Response time ---
13pm.test("Response time is under 500ms", function () {
14    pm.expect(pm.response.responseTime).to.be.below(500);
15});
16
17// --- Headers ---
18pm.test("Content-Type is JSON", function () {
19    pm.response.to.have.header("Content-Type", "application/json; charset=utf-8");
20});
21
22pm.test("Response has X-Request-ID header", function () {
23    pm.expect(pm.response.headers.get("X-Request-ID")).to.not.be.empty;
24});

Response Body Validation

Tests: Body Validationjavascript
1// --- Parse and validate response body ---
2const jsonData = pm.response.json();
3
4pm.test("Response has correct structure", function () {
5    pm.expect(jsonData).to.have.property("data");
6    pm.expect(jsonData).to.have.property("pagination");
7    pm.expect(jsonData.data).to.be.an("array");
8});
9
10pm.test("Users have required fields", function () {
11    jsonData.data.forEach(function (user) {
12        pm.expect(user).to.have.all.keys("id", "name", "email", "role");
13        pm.expect(user.id).to.be.a("number");
14        pm.expect(user.name).to.be.a("string").and.not.empty;
15        pm.expect(user.email).to.match(/^[^@]+@[^@]+\.[^@]+$/);
16        pm.expect(user.role).to.be.oneOf(["admin", "user", "moderator"]);
17    });
18});
19
20pm.test("Pagination values are correct", function () {
21    pm.expect(jsonData.pagination.page).to.equal(1);
22    pm.expect(jsonData.pagination.limit).to.equal(10);
23    pm.expect(jsonData.pagination.total).to.be.a("number");
24    pm.expect(jsonData.pagination.pages).to.be.at.least(1);
25});
26
27// --- Store data for the next request ---
28if (jsonData.data.length > 0) {
29    pm.environment.set("firstUserId", jsonData.data[0].id);
30    pm.environment.set("firstUserEmail", jsonData.data[0].email);
31}
32
33// --- Conditional test based on environment ---
34if (pm.environment.get("env") === "production") {
35    pm.test("Production: no test accounts in response", function () {
36        jsonData.data.forEach(function (user) {
37            pm.expect(user.email).to.not.contain("test");
38        });
39    });
40}

JSON Schema Validation

Schema validation ensures the API response structure matches a contract. This catches breaking changes early — missing fields, wrong types, or unexpected values.

Tests: Schema Validationjavascript
1// --- JSON Schema validation ---
2const schema = {
3    type: "object",
4    required: ["data", "pagination"],
5    properties: {
6        data: {
7            type: "array",
8            items: {
9                type: "object",
10                required: ["id", "name", "email", "role"],
11                properties: {
12                    id: { type: "integer" },
13                    name: { type: "string", minLength: 1 },
14                    email: { type: "string", format: "email" },
15                    role: {
16                        type: "string",
17                        enum: ["admin", "user", "moderator"]
18                    }
19                }
20            }
21        },
22        pagination: {
23            type: "object",
24            required: ["page", "limit", "total", "pages"],
25            properties: {
26                page: { type: "integer", minimum: 1 },
27                limit: { type: "integer", minimum: 1 },
28                total: { type: "integer", minimum: 0 },
29                pages: { type: "integer", minimum: 1 }
30            }
31        }
32    }
33};
34
35pm.test("Response matches JSON schema", function () {
36    pm.response.to.have.jsonSchema(schema);
37});
The pm Object Reference
  • pm.test(name, fn) — Define a named test assertion
  • pm.expect(value) — Chai-style assertion
  • pm.response.json() — Parse response body as JSON
  • pm.response.text() — Get response body as string
  • pm.response.code — HTTP status code (number)
  • pm.response.responseTime — Response time in ms
  • pm.response.headers — Response headers
  • pm.info.iteration — Current iteration index
  • pm.info.requestName — Name of the current request

Collection Runner

The Collection Runner executes all requests in a collection sequentially. It is Postman's built-in test runner — think of it as the "play" button for your entire test suite. Combined with data files, it enables powerful data-driven testing.

Running a Collection

  1. Open your collection and click Run (or use the Runner tab)
  2. Select the environment to use
  3. Set the number of iterations (how many times to run the full collection)
  4. Optionally set a delay between requests (in milliseconds)
  5. Optionally upload a data file (CSV or JSON) for data-driven testing
  6. Click Run Collection

Data-Driven Testing

Data-driven testing runs the same requests multiple times with different input data. Each row in a CSV or each object in a JSON array becomes one iteration.

test-data (CSV or JSON)json
1// Data file for Collection Runner (users.csv):
2// name,email,role,expectedStatus
3// John Doe,john@example.com,admin,200
4// Jane Smith,jane@example.com,user,200
5// ,invalid-email,user,400
6// Bob Wilson,bob@example.com,superadmin,400
7//
8// ----- OR as JSON (users.json): -----
9[
10  {
11    "name": "John Doe",
12    "email": "john@example.com",
13    "role": "admin",
14    "expectedStatus": 200
15  },
16  {
17    "name": "Jane Smith",
18    "email": "jane@example.com",
19    "role": "user",
20    "expectedStatus": 200
21  },
22  {
23    "name": "",
24    "email": "invalid-email",
25    "role": "user",
26    "expectedStatus": 400
27  },
28  {
29    "name": "Bob Wilson",
30    "email": "bob@example.com",
31    "role": "superadmin",
32    "expectedStatus": 400
33  }
34]

Using Data Variables in Tests

Data-Driven Testsjavascript
1// Request Body (using data variables from CSV/JSON):
2// POST {{baseUrl}}/api/users
3{
4    "name": "{{name}}",
5    "email": "{{email}}",
6    "role": "{{role}}"
7}
8
9// Test Script (validates against expected status from data file):
10pm.test("Status code matches expected: " + pm.iterationData.get("expectedStatus"), function () {
11    const expectedStatus = parseInt(pm.iterationData.get("expectedStatus"));
12    pm.response.to.have.status(expectedStatus);
13});
14
15pm.test("Iteration " + pm.info.iteration + ": Response is valid", function () {
16    if (pm.response.code === 200 || pm.response.code === 201) {
17        const jsonData = pm.response.json();
18        pm.expect(jsonData).to.have.property("id");
19        pm.expect(jsonData.name).to.equal(pm.iterationData.get("name"));
20    } else {
21        const jsonData = pm.response.json();
22        pm.expect(jsonData).to.have.property("error");
23    }
24});
25
26// Log iteration details to console
27console.log(
28    "Iteration " + pm.info.iteration +
29    " | User: " + pm.iterationData.get("name") +
30    " | Status: " + pm.response.code
31);
TipData-driven testing is excellent for testing boundary conditions, invalid inputs, and permission levels. Create a data file with both valid and invalid entries, and assert different expected status codes for each row.

Newman CLI

Newman is Postman's command-line collection runner. It lets you run Postman collections from the terminal, making it perfect for CI/CD integration. Newman supports all Postman features including environments, data files, and reporters.

Installation and Basic Usage

terminalbash
1# Install Newman globally
2npm install -g newman
3
4# Or as a project dev dependency
5npm install --save-dev newman
6
7# Install the HTML reporter for rich reports
8npm install -g newman-reporter-htmlextra
9
10# Run a collection from a file
11newman run my-collection.json
12
13# Run with an environment file
14newman run my-collection.json -e staging-environment.json
15
16# Run with a data file (CSV or JSON)
17newman run my-collection.json -d test-data.csv
18
19# Run with multiple iterations
20newman run my-collection.json -n 10
21
22# Run with a delay between requests (milliseconds)
23newman run my-collection.json --delay-request 500
24
25# Run with a specific folder from the collection
26newman run my-collection.json --folder "User CRUD"
27
28# Generate an HTML report
29newman run my-collection.json \
30  -r htmlextra \
31  --reporter-htmlextra-export ./reports/api-report.html
32
33# Combine multiple reporters
34newman run my-collection.json \
35  -r cli,htmlextra,junit \
36  --reporter-htmlextra-export ./reports/report.html \
37  --reporter-junit-export ./reports/junit.xml

CI/CD Integration (GitHub Actions)

.github/workflows/api-tests.ymlyaml
1# .github/workflows/api-tests.yml
2name: API Tests
3
4on:
5  push:
6    branches: [main]
7  pull_request:
8    branches: [main]
9  schedule:
10    - cron: '0 */6 * * *'  # Run every 6 hours
11
12jobs:
13  api-tests:
14    runs-on: ubuntu-latest
15    steps:
16      - uses: actions/checkout@v4
17
18      - uses: actions/setup-node@v4
19        with:
20          node-version: 20
21
22      - name: Install Newman and reporters
23        run: |
24          npm install -g newman
25          npm install -g newman-reporter-htmlextra
26
27      - name: Run API smoke tests
28        run: |
29          newman run collections/smoke-tests.json \
30            -e environments/staging.json \
31            -r cli,htmlextra,junit \
32            --reporter-htmlextra-export reports/smoke-report.html \
33            --reporter-junit-export reports/smoke-junit.xml \
34            --bail    # Stop on first failure
35
36      - name: Run full regression suite
37        if: github.event_name == 'schedule'
38        run: |
39          newman run collections/regression.json \
40            -e environments/staging.json \
41            -d test-data/users.csv \
42            -n 3 \
43            --delay-request 100 \
44            -r cli,htmlextra \
45            --reporter-htmlextra-export reports/regression-report.html
46
47      - name: Upload test reports
48        uses: actions/upload-artifact@v4
49        if: always()
50        with:
51          name: api-test-reports
52          path: reports/
53          retention-days: 14

NPM Scripts for Easy Execution

package.jsonjson
1// package.json scripts for Newman
2{
3  "scripts": {
4    "test:api": "newman run collections/api-tests.json -e environments/dev.json",
5    "test:api:staging": "newman run collections/api-tests.json -e environments/staging.json",
6    "test:api:prod": "newman run collections/api-tests.json -e environments/prod.json",
7    "test:smoke": "newman run collections/smoke.json -e environments/staging.json --bail",
8    "test:regression": "newman run collections/regression.json -e environments/staging.json -d data/users.csv -n 5",
9    "test:report": "newman run collections/api-tests.json -e environments/dev.json -r htmlextra --reporter-htmlextra-export reports/report.html"
10  }
11}
12
13// Running from npm:
14// npm run test:api
15// npm run test:api:staging
16// npm run test:smoke
WarningWhen running Newman in CI/CD, always export your collection and environment files from Postman and commit them to your repository. Do not rely on Postman's cloud API to fetch collections at runtime — it adds a dependency on Postman's servers and can cause flaky pipelines.

Performance Testing

While Postman is primarily an API functional testing tool, it can be used for basic performance and load testing. For lightweight performance validation, you can use Newman with multiple iterations and parallel execution. For heavy load testing, consider dedicated tools like k6, JMeter, or Gatling alongside Postman.

Load Testing with Newman

Newman supports running collections with many iterations and can be parallelized using shell scripts or tools like GNU Parallel to simulate concurrent users.

Load Testing with Newmanbash
1# ---- Load Testing with Newman + Parallel Execution ----
2
3# Run 50 iterations sequentially (basic load test)
4newman run collections/api-tests.json \
5  -e environments/staging.json \
6  -n 50 \
7  --delay-request 100
8
9# Run with timeout per request (3 seconds)
10newman run collections/api-tests.json \
11  -e environments/staging.json \
12  -n 100 \
13  --timeout-request 3000
14
15# ---- Parallel execution using GNU Parallel ----
16# Run 10 instances of Newman simultaneously (simulates 10 concurrent users)
17seq 10 | parallel -j 10 \
18  newman run collections/api-tests.json \
19    -e environments/staging.json \
20    -n 20 \
21    --delay-request 50 \
22    -r cli
23
24# ---- Using a shell script for parallel load ----
25#!/bin/bash
26COLLECTION="collections/api-tests.json"
27ENVIRONMENT="environments/staging.json"
28CONCURRENT_USERS=10
29ITERATIONS_PER_USER=20
30
31echo "Starting load test: $CONCURRENT_USERS users x $ITERATIONS_PER_USER iterations"
32
33for i in $(seq 1 $CONCURRENT_USERS); do
34  newman run "$COLLECTION" \
35    -e "$ENVIRONMENT" \
36    -n "$ITERATIONS_PER_USER" \
37    --delay-request 50 \
38    --reporter-cli-silent \
39    > "reports/user-$i.log" 2>&1 &
40done
41
42# Wait for all background processes to finish
43wait
44echo "Load test complete. Check reports/ for results."

Performance Monitoring in Test Scripts

Add response time assertions and metrics collection directly in your Postman test scripts to track API performance over time:

Performance Metrics Scriptjavascript
1// Performance monitoring in Postman Test Scripts
2
3// Track response times and store them
4const responseTime = pm.response.responseTime;
5const requestName = pm.info.requestName;
6
7pm.test("Response time is acceptable (< 1000ms)", function () {
8    pm.expect(responseTime).to.be.below(1000);
9});
10
11// Categorize response time
12let performanceGrade;
13if (responseTime < 200) {
14    performanceGrade = "EXCELLENT";
15} else if (responseTime < 500) {
16    performanceGrade = "GOOD";
17} else if (responseTime < 1000) {
18    performanceGrade = "ACCEPTABLE";
19} else {
20    performanceGrade = "SLOW";
21}
22
23console.log(
24    requestName + " | " +
25    responseTime + "ms | " +
26    performanceGrade
27);
28
29// Store metrics for aggregation across iterations
30let metrics = JSON.parse(pm.environment.get("perfMetrics") || "[]");
31metrics.push({
32    request: requestName,
33    time: responseTime,
34    status: pm.response.code,
35    iteration: pm.info.iteration,
36    timestamp: new Date().toISOString()
37});
38pm.environment.set("perfMetrics", JSON.stringify(metrics));
39
40// After the final iteration, calculate averages (in collection-level test)
41if (pm.info.iteration === pm.info.iterationCount - 1) {
42    const allMetrics = JSON.parse(pm.environment.get("perfMetrics") || "[]");
43    const times = allMetrics.map(m => m.time);
44    const avg = times.reduce((a, b) => a + b, 0) / times.length;
45    const max = Math.max(...times);
46    const min = Math.min(...times);
47    const p95 = times.sort((a, b) => a - b)[Math.floor(times.length * 0.95)];
48
49    console.log("=== Performance Summary ===");
50    console.log("Requests: " + times.length);
51    console.log("Average: " + avg.toFixed(2) + "ms");
52    console.log("Min: " + min + "ms");
53    console.log("Max: " + max + "ms");
54    console.log("P95: " + p95 + "ms");
55}

Postman Monitors

Postman Monitors run collections on a schedule (e.g., every 5 minutes) from Postman's cloud infrastructure. They are useful for:

  • Uptime monitoring — Detect when APIs go down
  • Performance regression — Track response time trends over days/weeks
  • SLA validation — Verify APIs meet response time commitments
  • Multi-region testing — Run from different geographic locations
ApproachConcurrent UsersBest ForLimitations
Collection Runner1 (sequential)Functional smoke testsNo concurrency, GUI only
Newman (single)1 (sequential)CI/CD functional testsNo concurrency
Newman (parallel)10-50+Basic load testingManual setup, limited metrics
Postman Monitors1 per locationUptime and SLA monitoringLimited iterations, cloud only
k6 / JMeter / Gatling1000s+Heavy load and stress testingSeparate tool, different scripting
When to Use WhatUse Postman/Newman for functional API tests and lightweight performance baselines. For serious load testing (hundreds or thousands of concurrent users), use a dedicated performance testing tool. Many teams maintain both: Postman collections for correctness and k6 scripts for load — often sharing the same test scenarios.

Full Example: Complete API Testing Workflow

Let us put everything together with a complete API testing workflow that authenticates, performs CRUD operations, validates responses, and cleans up after itself. This mirrors a real-world test suite you would run in CI/CD.

Collection Structure

Collection Overviewtext
1// Collection structure for the full workflow:
2//
3// API Testing Workflow/
4// ├── Auth/
5// │   └── POST Login
6// ├── Products CRUD/
7// │   ├── POST Create Product
8// │   ├── GET  Read Product
9// │   ├── PUT  Update Product
10// │   ├── DEL  Delete Product
11// │   └── GET  Verify Deletion
12// └── Cleanup/
13//     └── POST Logout
14//
15// Run order: Requests execute top-to-bottom when using Collection Runner
16// Each request's Tests tab stores data for the next request via variables
17//
18// Newman command to run the full workflow:
19// newman run api-workflow.json \
20//   -e staging.json \
21//   -r cli,htmlextra \
22//   --reporter-htmlextra-export report.html

Step 1: Authenticate

The first request logs in and stores the auth token in an environment variable. All subsequent requests use this token via the {{authToken}} variable.

POST /api/auth/loginjavascript
1// ===== STEP 1: Authenticate =====
2// POST {{baseUrl}}/api/auth/login
3//
4// Request Body:
5{
6  "email": "{{testUserEmail}}",
7  "password": "{{testUserPassword}}"
8}
9
10// Tests tab:
11pm.test("Login successful", function () {
12    pm.response.to.have.status(200);
13
14    const jsonData = pm.response.json();
15
16    pm.expect(jsonData).to.have.property("token");
17    pm.expect(jsonData).to.have.property("user");
18    pm.expect(jsonData.user.email).to.equal(pm.environment.get("testUserEmail"));
19
20    // Store token for all subsequent requests
21    pm.environment.set("authToken", jsonData.token);
22    pm.environment.set("currentUserId", jsonData.user.id);
23
24    console.log("Authenticated as: " + jsonData.user.email);
25});
26
27pm.test("Response time under 1s", function () {
28    pm.expect(pm.response.responseTime).to.be.below(1000);
29});

Steps 2-4: CRUD Operations

Create a product, read it back, then update it. Each step validates the response and stores data needed by the next step.

CRUD Operationsjavascript
1// ===== STEP 2: CREATE a resource =====
2// POST {{baseUrl}}/api/products
3// Headers: Authorization: Bearer {{authToken}}
4//
5// Request Body:
6{
7  "name": "Wireless Headphones Pro",
8  "sku": "{{$guid}}",
9  "price": 79.99,
10  "category": "electronics",
11  "stock": 150,
12  "description": "Premium noise-canceling wireless headphones"
13}
14
15// Tests tab:
16pm.test("Product created (201)", function () {
17    pm.response.to.have.status(201);
18    const product = pm.response.json();
19
20    pm.expect(product).to.have.property("id");
21    pm.expect(product.name).to.equal("Wireless Headphones Pro");
22    pm.expect(product.price).to.equal(79.99);
23
24    // Store product ID for subsequent CRUD operations
25    pm.environment.set("createdProductId", product.id);
26    console.log("Created product ID: " + product.id);
27});
28
29// ===== STEP 3: READ the resource =====
30// GET {{baseUrl}}/api/products/{{createdProductId}}
31// Headers: Authorization: Bearer {{authToken}}
32
33// Tests tab:
34pm.test("Product retrieved (200)", function () {
35    pm.response.to.have.status(200);
36    const product = pm.response.json();
37
38    pm.expect(product.id).to.equal(
39        parseInt(pm.environment.get("createdProductId"))
40    );
41    pm.expect(product.name).to.equal("Wireless Headphones Pro");
42    pm.expect(product.stock).to.equal(150);
43});
44
45// ===== STEP 4: UPDATE the resource =====
46// PUT {{baseUrl}}/api/products/{{createdProductId}}
47// Headers: Authorization: Bearer {{authToken}}
48//
49// Request Body:
50{
51  "price": 69.99,
52  "stock": 200,
53  "description": "Premium noise-canceling wireless headphones - SALE"
54}
55
56// Tests tab:
57pm.test("Product updated (200)", function () {
58    pm.response.to.have.status(200);
59    const product = pm.response.json();
60
61    pm.expect(product.price).to.equal(69.99);
62    pm.expect(product.stock).to.equal(200);
63    pm.expect(product.description).to.contain("SALE");
64});

Steps 5-7: Cleanup and Verification

Delete the created resource, verify it no longer exists, and clean up environment variables. A proper test always cleans up after itself.

Cleanup & Verificationjavascript
1// ===== STEP 5: DELETE the resource (cleanup) =====
2// DELETE {{baseUrl}}/api/products/{{createdProductId}}
3// Headers: Authorization: Bearer {{authToken}}
4
5// Tests tab:
6pm.test("Product deleted (204)", function () {
7    pm.response.to.have.status(204);
8});
9
10// ===== STEP 6: VERIFY deletion =====
11// GET {{baseUrl}}/api/products/{{createdProductId}}
12// Headers: Authorization: Bearer {{authToken}}
13
14// Tests tab:
15pm.test("Product no longer exists (404)", function () {
16    pm.response.to.have.status(404);
17});
18
19pm.test("Error message is appropriate", function () {
20    const jsonData = pm.response.json();
21    pm.expect(jsonData.error).to.contain("not found");
22});
23
24// ===== STEP 7: Cleanup environment variables =====
25// (In the last request's Tests tab)
26pm.environment.unset("createdProductId");
27pm.environment.unset("authToken");
28pm.environment.unset("currentUserId");
29
30console.log("=== Test Complete: All CRUD operations verified ===");
31console.log("Environment variables cleaned up.");

What This Example Demonstrates

  • Request chaining — Each request passes data to the next via environment variables (authToken, createdProductId).
  • Complete CRUD coverage — Create, Read, Update, Delete, and verify deletion — the fundamental API test pattern.
  • Status code validation — Each operation asserts the correct HTTP status (201 for create, 200 for read/update, 204 for delete, 404 for missing).
  • Body validation — Responses are parsed and individual fields are checked for correct values and types.
  • Test isolation — The suite creates its own data, tests it, and removes it. It does not depend on pre-existing data in the database.
  • Environment cleanup — Variables are unset at the end to prevent stale data from affecting future runs.
TipExport this collection and run it with Newman in your CI/CD pipeline:
newman run api-workflow.json -e staging.json -r cli,htmlextra
This gives you automated API regression testing on every deployment.
Next StepsNow that you understand the full Postman workflow, explore these advanced topics: API mocking with Postman Mock Server, contract testing with schemas, Postman Flows for visual API workflows, and integrating Newman results with monitoring dashboards like Grafana or Datadog.