Complete Reference · Beginner to Expert
Everything you need to understand, test, and master APIs — from HTTP basics to advanced automation scripts.
The foundation — understanding what you're actually testing
Application Programming Interface — a contract between two software systems defining how they communicate. An API exposes specific endpoints that accept requests and return responses without exposing internal implementation details.
APIs are the backbone of modern apps. A broken API breaks everything built on top of it. API testing catches issues early, validates business logic, and ensures reliability before the UI is even built. Faster to test, cheaper to fix.
| Type | Description | Common Use |
|---|---|---|
| REST | Uses HTTP methods, stateless, JSON responses | Web/mobile apps — most common |
| SOAP | XML-based, strict contract via WSDL | Banking, legacy enterprise systems |
| GraphQL | Query language — ask for exactly what you need | Flexible frontends (GitHub, Shopify) |
| gRPC | Google's binary protocol, very fast | Microservices, internal services |
| WebSocket | Persistent two-way connection | Real-time chat, live dashboards |
The protocol that powers every REST API request
POST /api/users HTTP/1.1 Host: api.example.com Content-Type: application/json Authorization: Bearer eyJhbGci... Accept: application/json // Request Body (payload) { "name": "Alice", "email": "alice@example.com" }
HTTP/1.1 201 Created Content-Type: application/json X-Request-Id: abc-123 // Response Body { "id": 42, "name": "Alice", "email": "alice@example.com", "createdAt": "2026-05-22T10:00:00Z" }
https://api.site.com/v1/users?role=admin&page=2
Scheme → https
Host → api.site.com
Path → /v1/users
Query → role=admin&page=2
Embedded in the URL path, identify a specific resource.
GET /users/42
Here 42 is the path param (user ID).
After ?, used for filtering, sorting, pagination.
GET /users?role=admin&sort=asc&limit=20
CRUD operations mapped to HTTP — know these cold
| Method | CRUD | Description | Has Body? | Idempotent? |
|---|---|---|---|---|
| GET | Read | Retrieve a resource. Never modify data. | No | Yes |
| POST | Create | Create a new resource. Body contains data. | Yes | No |
| PUT | Update (full) | Replace entire resource. Send all fields. | Yes | Yes |
| PATCH | Update (partial) | Update specific fields only. | Yes | Sometimes |
| DELETE | Delete | Remove a resource by ID. | Rarely | Yes |
| HEAD | Read | Like GET but returns headers only, no body. | No | Yes |
| OPTIONS | Meta | Returns allowed methods for a URL. Used in CORS. | No | Yes |
The server's answer to your request — memorise these
| 200 | OK — standard success for GET/PUT |
| 201 | Created — resource created (POST) |
| 202 | Accepted — async operation queued |
| 204 | No Content — success, no body (DELETE) |
| 301 | Moved Permanently — update your bookmarks |
| 302 | Found — temporary redirect |
| 304 | Not Modified — use cached version |
| 400 | Bad Request — malformed body/params |
| 401 | Unauthorized — not authenticated |
| 403 | Forbidden — authenticated but no permission |
| 404 | Not Found — resource doesn't exist |
| 405 | Method Not Allowed — wrong HTTP verb |
| 409 | Conflict — duplicate resource |
| 422 | Unprocessable Entity — validation failed |
| 429 | Too Many Requests — rate limited |
| 500 | Internal Server Error — generic crash |
| 501 | Not Implemented — feature not built |
| 502 | Bad Gateway — proxy got bad response |
| 503 | Service Unavailable — down/overloaded |
| 504 | Gateway Timeout — upstream timed out |
Metadata attached to requests and responses
Content-Type: application/json Accept: application/json Authorization: Bearer <token> X-API-Key: abc123 Accept-Language: en-US Cache-Control: no-cache User-Agent: PostmanRuntime/7.32
Content-Type: application/json Content-Length: 248 X-Request-Id: a7b2c3d4 X-Rate-Limit: 100 X-Rate-Remaining: 87 Set-Cookie: session=xyz; HttpOnly ETag: "abc123"
Content-Type: application/json is one of the most common causes of 400 errors. Always verify both request and response headers in your test assertions.Proving identity and controlling access — critical for secure API testing
Simplest form. A static key passed in a header or query param. No expiration unless rotated manually.
// In Header X-API-Key: sk_live_abc123xyz // In Query Param GET /data?api_key=sk_live_abc123xyz
Username and password encoded in Base64. Insecure over HTTP — always use HTTPS.
// Format: Base64(username:password) Authorization: Basic dXNlcjpwYXNz // In curl curl -u username:password https://api.example.com
Most common for modern REST APIs. Login once to get a token, use it in subsequent requests. Tokens expire.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... // JWT has 3 parts: Header.Payload.Signature // Decode at jwt.io -- NEVER expose the secret key
Industry standard for delegated access. User authorises an app to act on their behalf.
// Step 1: Get Authorization Code GET /oauth/authorize?client_id=abc&response_type=code // Step 2: Exchange for Access Token POST /oauth/token { "code": "xyz", "grant_type": "authorization_code" } // Step 3: Use the Token Authorization: Bearer <access_token>
Pick the right tool for the right job
The most popular GUI tool. Supports collections, environments, test scripts (JavaScript), mock servers, and CI/CD via Newman. Best for manual + automated testing with team collaboration.
Lightweight alternative to Postman. Great for GraphQL and gRPC. Clean UI, plugin ecosystem. Preferred by developers who want minimal overhead.
Command-line HTTP client. Available everywhere. Ideal for quick tests, CI pipelines, and scripting. No GUI — pure terminal power.
Java DSL for REST API testing. Integrates natively with JUnit/TestNG and Maven. Best for Java teams doing automated regression testing.
Simple, readable HTTP library for Python. Combine with Pytest for a powerful automation framework. Great for scripting and data-driven tests.
Postman's CLI runner. Runs Postman collections from the terminal or CI pipelines (Jenkins, GitHub Actions). Export your Postman collection → run in CI.
API documentation standard. The swagger.json / openapi.yaml file defines all endpoints, parameters, and responses. Use it to auto-generate tests.
Industry-standard performance and load testing tool. Tests how APIs behave under high traffic — thousands of concurrent users. Essential for performance testing.
Different dimensions of quality to validate
| Test Type | What it checks | Example |
|---|---|---|
| Functional | API does what it's supposed to do | POST /users creates a user with correct fields |
| Validation | Request/response schema and data types match spec | id is always an integer, not a string |
| Authentication | Endpoints reject unauthenticated/unauthorised access | Missing token → 401, wrong role → 403 |
| Error Handling | Meaningful errors for bad input | Empty required field → 400 with clear message |
| Negative Testing | API handles invalid/unexpected input gracefully | String instead of integer → 400, not 500 |
| Performance | Response time under normal and peak load | GET /products responds under 200ms |
| Load Testing | System stability under sustained load | 1000 concurrent users for 10 minutes |
| Stress Testing | Find the breaking point | Ramp up until 503/504 errors appear |
| Security | Prevent data leaks, injection, IDOR, etc. | User A cannot access User B's data |
| Contract Testing | Provider API matches consumer expectations | Pact framework between microservices |
| Regression | New changes don't break existing functionality | Run full suite after every deploy |
| End-to-End | Full user flow through multiple APIs | Register → Login → Buy Product → Get Invoice |
A test without assertions is just a request — assert everything that matters
Always assert the exact status code. Don't just check "it didn't error" — a 200 when you expected 201 is still a bug.
Assert that response time is under your SLA. Common threshold: 200ms for simple reads, 500ms for writes.
Check field values, data types, required fields present, no extra sensitive fields leaking in the response.
Validate the structure matches the API contract. Use JSON Schema validation — catch missing/renamed fields automatically.
Check Content-Type is correct. Check security headers are present like X-Frame-Options and Strict-Transport-Security.
On failure, check error message is informative but doesn't expose internal stack traces or DB query details.
From basic requests to automated test suites
// 1. Check status code pm.test("Status is 200", () => { pm.response.to.have.status(200); }); // 2. Check response time pm.test("Responds within 500ms", () => { pm.expect(pm.response.responseTime).to.be.below(500); }); // 3. Parse and check JSON body const json = pm.response.json(); pm.test("User name is Alice", () => { pm.expect(json.name).to.eql("Alice"); }); // 4. Check field exists and type pm.test("ID is a number", () => { pm.expect(json.id).to.be.a("number"); }); // 5. Check header pm.test("Content-Type is JSON", () => { pm.expect(pm.response.headers.get("Content-Type")) .to.include("application/json"); }); // 6. Store value in environment variable pm.environment.set("userId", json.id);
// Generate dynamic timestamp pm.environment.set("timestamp", new Date().toISOString()); // Generate random email for test data const rand = Math.random().toString(36).substring(7); pm.environment.set("testEmail", `test_${rand}@example.com`); // Read environment variable const token = pm.environment.get("authToken");
// Use double curly braces in URL GET {{baseUrl}}/users/{{userId}} // In request body { "email": "{{testEmail}}", "token": "{{authToken}}" } // Scope priority: local > data > environment > collection > global
# Install Newman npm install -g newman # Run a collection with environment newman run collection.json -e environment.json # Run with HTML report newman run collection.json -e env.json \ --reporters html \ --reporter-html-export report.html # Run with data file (data-driven testing) newman run collection.json -d testdata.csv
Fluent Java DSL for elegant API test automation
import static io.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; // Simple GET test with assertions given() .baseUri("https://api.example.com") .header("Authorization", "Bearer " + token) .when() .get("/users/1") .then() .statusCode(200) .body("name", equalTo("Alice")) .body("id", equalTo(1)) .time(lessThan(500L));
String requestBody = """ { "name": "Bob", "email": "bob@example.com" } """; Response response = given() .contentType("application/json") .body(requestBody) .when() .post("/users") .then() .statusCode(201) .body("name", equalTo("Bob")) .extract().response(); int newId = response.path("id"); // extract for chaining
import static io.restassured.module.jsv.JsonSchemaValidator.*; given() .get("/users/1") .then() .statusCode(200) .body(matchesJsonSchemaInClasspath("user-schema.json")); // user-schema.json in src/test/resources/ // Validates field types, required fields, formats automatically
requests + pytest = powerful, readable automation
import requests BASE_URL = "https://api.example.com" TOKEN = "your_bearer_token" HEADERS = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json" } # GET response = requests.get(f"{BASE_URL}/users/1", headers=HEADERS) print(response.status_code) # 200 print(response.json()) # parsed dict print(response.elapsed.total_seconds()) # response time print(response.headers["Content-Type"]) # response header # POST payload = {"name": "Bob", "email": "bob@example.com"} r = requests.post(f"{BASE_URL}/users", json=payload, headers=HEADERS) print(r.status_code) # 201 # PUT (full update) r = requests.put(f"{BASE_URL}/users/1", json=payload, headers=HEADERS) # PATCH (partial update) r = requests.patch(f"{BASE_URL}/users/1", json={"name": "Alice"}, headers=HEADERS) # DELETE r = requests.delete(f"{BASE_URL}/users/1", headers=HEADERS) print(r.status_code) # 204
import pytest import requests BASE = "https://api.example.com" @pytest.fixture(scope="module") def auth_token(): r = requests.post(f"{BASE}/auth/login", json={ "username": "testuser", "password": "pass123" }) return r.json()["token"] def test_get_user(auth_token): r = requests.get(f"{BASE}/users/1", headers={"Authorization": f"Bearer {auth_token}"}) assert r.status_code == 200 assert r.json()["id"] == 1 assert isinstance(r.json()["name"], str) assert r.elapsed.total_seconds() < 0.5 def test_create_user(auth_token): payload = {"name": "Test User", "email": "test@example.com"} r = requests.post(f"{BASE}/users", json=payload, headers={"Authorization": f"Bearer {auth_token}"}) assert r.status_code == 201 assert "id" in r.json() def test_unauthorized_access(): r = requests.get(f"{BASE}/users/1") # no token assert r.status_code == 401 @pytest.mark.parametrize("user_id", [1, 2, 3]) def test_multiple_users(user_id, auth_token): r = requests.get(f"{BASE}/users/{user_id}", headers={"Authorization": f"Bearer {auth_token}"}) assert r.status_code == 200
# Install libraries pip install requests pytest pytest-html # Run all tests pytest # Run with verbose output pytest -v # Run specific test file pytest test_users.py # Run and generate HTML report pytest --html=report.html --self-contained-html # Run only tests marked with a tag pytest -m smoke
OWASP Top 10 for APIs — vulnerabilities you must test for
| Vulnerability | What it is | How to test |
|---|---|---|
| IDOR | Insecure Direct Object Reference — access others' data by changing an ID | Access /users/2 while logged in as user 1 → should get 403 |
| Broken Auth | Weak token validation, no expiry, predictable tokens | Use expired/forged JWT; test token rotation |
| SQL Injection | Malicious SQL in input fields executed by the database | Send ' OR '1'='1 in query params, check response |
| XSS via API | Script tags stored via API, executed when rendered | POST a script tag as a name field, verify it's escaped |
| Mass Assignment | User sends extra fields (isAdmin: true) that get saved | Add extra fields to POST body, verify they're ignored |
| Excessive Data Exposure | API returns sensitive fields not needed by client | Check if passwords, tokens, PII appear in responses |
| Rate Limiting Missing | No throttle = brute-force/DoS risk | Send 1000 requests rapidly, expect 429 after threshold |
| Broken Object Level Auth | User can modify/delete resources they don't own | Try DELETE /orders/999 with a different user's token |
# Test 1: No auth token -- expect 401 curl -X GET https://api.example.com/users/1 # Test 2: IDOR -- access another user's data -- expect 403 curl -H "Authorization: Bearer user1_token" \ https://api.example.com/users/2 # Test 3: SQL injection in query param curl "https://api.example.com/search?q=%27%20OR%20%271%27%3D%271" # Test 4: Mass assignment -- send isAdmin field curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"hacker","isAdmin":true}' # Verify: isAdmin should be ignored in response
Habits that separate good testers from great ones
should_return_404_when_user_not_foundUse this for every new API endpoint you test