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
pmAPI. - 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
| Aspect | API Testing | Performance Testing |
|---|---|---|
| Goal | Verify correctness, data integrity, error handling | Measure speed, throughput, stability under load |
| Scope | Individual endpoints, workflows, edge cases | System-wide behavior under concurrent load |
| Key metrics | Status codes, response body, schema validation | Response time, throughput (req/s), error rate, P95/P99 |
| Tools in Postman | Test Scripts, Collection Runner, Newman | Collection Runner iterations, Newman parallel, Monitors |
| When to run | Every commit, PR, deployment | Before releases, after infrastructure changes |
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.
- Visit
postman.com/downloadsand download the app for your OS - Install and launch Postman
- Create a free account or sign in (required for syncing and collaboration)
- 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.
- Click New in the sidebar, then select Collection
- Name your collection (e.g., "User API Tests")
- Add folders to organize by feature (e.g., "Auth", "Users", "Products")
- Add requests to each folder
- Set collection-level variables and scripts that apply to all requests
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.
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.
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.
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- 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
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.
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.
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}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:
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:
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});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
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
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.
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});pm.test(name, fn)— Define a named test assertionpm.expect(value)— Chai-style assertionpm.response.json()— Parse response body as JSONpm.response.text()— Get response body as stringpm.response.code— HTTP status code (number)pm.response.responseTime— Response time in mspm.response.headers— Response headerspm.info.iteration— Current iteration indexpm.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
- Open your collection and click Run (or use the Runner tab)
- Select the environment to use
- Set the number of iterations (how many times to run the full collection)
- Optionally set a delay between requests (in milliseconds)
- Optionally upload a data file (CSV or JSON) for data-driven testing
- 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.
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
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);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
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.xmlCI/CD Integration (GitHub Actions)
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: 14NPM Scripts for Easy Execution
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:smokePerformance 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.
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:
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
| Approach | Concurrent Users | Best For | Limitations |
|---|---|---|---|
| Collection Runner | 1 (sequential) | Functional smoke tests | No concurrency, GUI only |
| Newman (single) | 1 (sequential) | CI/CD functional tests | No concurrency |
| Newman (parallel) | 10-50+ | Basic load testing | Manual setup, limited metrics |
| Postman Monitors | 1 per location | Uptime and SLA monitoring | Limited iterations, cloud only |
| k6 / JMeter / Gatling | 1000s+ | Heavy load and stress testing | Separate tool, different scripting |
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
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.htmlStep 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.
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.
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.
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.
newman run api-workflow.json -e staging.json -r cli,htmlextraThis gives you automated API regression testing on every deployment.