Module 2: HTTP Deep Dive

API Testing & SQL · Module 2

HTTP Deep Dive: Methods, Status Codes & Headers

Every API you will ever test speaks HTTP. When a mobile app loads your profile, when a website saves a form, when one microservice calls another — it is all HTTP requests and responses travelling across a network. If you understand HTTP deeply, API testing stops feeling like guesswork and starts feeling like reading a conversation. This module gives you that fluency.

Learning objectives

By the end of this lesson you will be able to:

  • Explain what HTTP is and how the request–response model works.
  • Choose the correct HTTP method for any operation, and explain safety and idempotency — two ideas testers are constantly asked about.
  • Interpret every status-code family and the specific codes you will meet daily, and know how to react to each as a tester.
  • Read request and response headers and explain what each common header is for.
  • Break any real HTTP request and response into its parts and spot what is wrong.

What HTTP actually is

HTTP (HyperText Transfer Protocol) is a set of rules for how a client (the one asking — a browser, a mobile app, Postman, your test script) and a server (the one answering — the API) exchange messages. The client sends a request; the server sends back exactly one response. That is the whole model: request in, response out.

An analogy that will stick

Think of HTTP like ordering at a restaurant. You (the client) give the waiter a specific order (the request): what you want (the method and URL), any special instructions (the headers), and sometimes a written note with details (the body). The kitchen (the server) does the work and the waiter returns with either your dish (a success response) or a message like “we’re out of that” (an error response with a reason). You never walk into the kitchen yourself — you only ever see the response. API testing is checking that the right order always produces the right result, and that bad orders fail gracefully.

HTTP is “stateless” — and why that matters to you

Each HTTP request is independent: the server does not automatically remember your previous request. If the server needs to know “who are you?” or “are you logged in?”, that information must be sent again with every request — usually in a header such as Authorization or via a cookie. As a tester this explains a very common bug class: a request that works right after login but fails later because the token expired or was not resent. Statelessness is why you must always check how authentication travels on each call.

HTTP methods (verbs): say what you want to do

The method is the verb of the request — it tells the server what kind of action you intend. The four actions that cover almost everything are known as CRUD: Create, Read, Update, Delete. Here is how the methods map, with a running example of a /users resource:

Method CRUD action Typical use & example Has a body?
GET Read Fetch data. GET /users/42 returns user 42. No
POST Create Create something new. POST /users with a JSON body creates a user and usually returns 201 Created. Yes
PUT Update (replace) Replace a whole resource. PUT /users/42 sends the complete user object; missing fields may be wiped. Yes
PATCH Update (partial) Change only some fields. PATCH /users/42 with {"email":"new@x.com"} updates just the email. Yes
DELETE Delete Remove a resource. DELETE /users/42 often returns 204 No Content. Usually no

Two more you will occasionally see: HEAD (like GET but returns only headers, no body — handy for checking if something exists), and OPTIONS (asks the server which methods are allowed; browsers use it automatically for CORS “preflight” checks).

Safety and idempotency (interviewers love this)

These two words sound academic but describe real behaviour you must test.

Safe
A method is safe if it does not change data on the server. GET is safe — reading a user should never modify it. If a GET changes something, that is a serious bug.
Idempotent
A method is idempotent if making the same call many times has the same effect as making it once. GET, PUT and DELETE are idempotent: deleting user 42 twice still leaves user 42 deleted. POST is not idempotent: posting the same “create order” twice may create two orders.

Why a tester cares

Idempotency is behind one of the most expensive real-world bugs: the user taps “Pay”, the network is slow, they tap again, and they get charged twice because the POST was not protected. Part of your job is to test exactly these cases: double-submit, retry after timeout, refresh after submit. If a method is supposed to be idempotent, prove that calling it twice is safe.

Status codes: how the server tells you what happened

Every response carries a three-digit status code. The first digit tells you the family — the overall outcome — and the full code tells you the specifics. Learn the families first; the rest becomes easy.

Range Family meaning What it tells a tester
1xx Informational Rare in day-to-day testing; the request was received and processing continues (e.g. 100 Continue).
2xx Success It worked. Verify you also got the right data, not just a 2xx.
3xx Redirection The resource is elsewhere or unchanged. Check the Location header or caching behaviour.
4xx Client error You (the request) did something wrong — bad data, missing auth, wrong URL. The fix is in the request.
5xx Server error The server broke. The request may be perfectly valid — this is a defect to raise against the backend.

Memory hook: 4xx = “you messed up”, 5xx = “the server messed up.” A 500 is never the client’s fault to fix — it is a bug report.

The specific codes you must know cold

Code Name When you see it & what to do
200 OK Standard success for GET/PUT/PATCH. Now validate the body.
201 Created A POST created something. Expect a Location header and the new resource in the body.
204 No Content Success with nothing to return (common after DELETE). Body should be empty — test that it is.
301/302 Moved / Found Redirect. Follow the Location header; watch for http→https redirects.
400 Bad Request Malformed or invalid input. Perfect for negative testing — send junk and confirm you get 400, not 500.
401 Unauthorized Not authenticated — missing/expired/invalid token. (The name is misleading: it really means “unauthenticated”.)
403 Forbidden Authenticated, but not allowed. A logged-in normal user hitting an admin endpoint should get 403.
404 Not Found No such resource. Request a non-existent ID and confirm you get 404, not a 200 with empty data.
409 Conflict Clashes with current state — e.g. registering an email that already exists.
422 Unprocessable Content Syntax was fine but the data failed business validation (e.g. age = -5).
429 Too Many Requests Rate limit hit. Check for a Retry-After header.
500 Internal Server Error Unhandled server crash. Always raise this — and capture what request triggered it.
503 Service Unavailable Server overloaded or down for maintenance.

Tutor tip: 401 vs 403 is a classic trap

Remember it as a nightclub: 401 = the bouncer does not know who you are (you have no valid ID / token). 403 = the bouncer knows exactly who you are, but you are not on the VIP list (you are allowed in the building, just not this room). Testing both paths — no token, and a valid token for the wrong role — is core security testing.

Headers: the metadata that controls the conversation

Headers are key–value pairs that travel with both requests and responses. They do not contain the main data (that is the body) — they describe it and control how it is handled. These are the ones you will touch most:

Common request headers (client → server)

Content-Type
Tells the server the format of the body you are sending, e.g. application/json. Send JSON but forget this header and many servers reject or misread it — a frequent cause of mysterious 400s.
Authorization
Carries your credentials, usually Bearer <token>. This is how a stateless server knows who you are on every call.
Accept
Tells the server what format you want back, e.g. Accept: application/json.

Common response headers (server → client)

Content-Type
The format of the body coming back. Verify it matches what you asked for.
Location
After a 201 Created, the URL of the new resource — follow it to confirm the thing really exists.
Cache-Control / ETag
Control caching. Relevant when testing why “old” data keeps appearing.
X-RateLimit-* / Retry-After
How many calls you have left and when you can try again — essential for testing rate-limited APIs.

Anatomy of a full request and response

Let us put it all together. Here is a complete request to create a user, exactly as it travels:

POST /users HTTP/1.1                 <- method + path + protocol
Host: api.example.com                <- which server
Content-Type: application/json       <- the body is JSON
Authorization: Bearer eyJhbGci...    <- who I am
Accept: application/json             <- what I want back

{                                    <- blank line, then the body
  "name": "Asha",
  "email": "asha@example.com"
}

And the response the server sends back:

HTTP/1.1 201 Created                 <- status line
Content-Type: application/json       <- body format
Location: /users/42                  <- where the new user lives

{
  "id": 42,
  "name": "Asha",
  "email": "asha@example.com",
  "createdAt": "2026-01-12T09:30:00Z"
}

As a tester, you read this top to bottom and ask: Is the status correct (201, not 200)? Is there a Location? Does the body echo what I sent, plus a server-generated id and createdAt? Those questions become your assertions — which is exactly what the next module (Postman) and Module 4 (Validating Responses) teach you to automate.

Query params vs path params — a quick but important distinction

  • Path parameter — identifies a specific resource and is part of the URL path: /users/42 (user number 42).
  • Query parameter — filters, sorts or paginates, and comes after a ?: /users?role=admin&page=2&sort=name. Multiple params are joined with &.

Common mistakes to avoid

  • Trusting the status code alone. A 200 OK with the wrong or empty body is still a bug. Always validate the body too.
  • Forgetting Content-Type. Sending JSON without Content-Type: application/json causes confusing 400s.
  • Confusing 401 and 403. Get this wrong in a bug report and developers chase the wrong fix.
  • Reporting a 404 for a missing optional field. 404 is about the whole resource not existing, not a single empty field.
  • Treating a 500 as a test-script problem. 500 is a backend defect — capture the exact request and raise it.

Hands-on exercise

You are testing a blog API with a /posts resource. Using a free tool like httpbin.org or Postman (next module), work through the following:

  1. For each operation, write the correct method + URL and the success status you expect: (a) fetch all posts, (b) fetch post 7, (c) create a new post, (d) replace post 7 entirely, (e) change only the title of post 7, (f) delete post 7.
  2. Now the negative cases — name the status code you’d expect for: (g) requesting post 9999 which does not exist, (h) creating a post with no auth token, (i) creating a post with a malformed JSON body, (j) a logged-in reader trying to delete someone else’s post.
  3. Write the full HTTP request (method line, three headers, and a JSON body) for creating a post titled “My First Post”.

Knowledge check

  1. Which method partially updates a resource, and why is that different from PUT?
  2. Explain the difference between 401 and 403 in one sentence each.
  3. Which request header tells the server the body is JSON?
  4. Does a 500 error mean the client did something wrong? What should a tester do when they see one?
  5. Why is POST not idempotent, and what real bug does that cause?
Show answers
  1. PATCH sends only the fields to change; PUT replaces the entire resource, so fields you omit from a PUT may be erased.
  2. 401 = not authenticated (no/invalid credentials). 403 = authenticated but not permitted to do this action.
  3. Content-Type: application/json.
  4. No — a 500 is a server fault. The tester should capture the exact request that caused it and raise a defect against the backend.
  5. Because each POST can create a new resource, so repeating it creates duplicates — e.g. a double-tapped “Pay” button charging the customer twice.

Key takeaways

  • HTTP is a stateless request–response conversation; auth must travel on every call.
  • Methods map to CRUD; know safety (GET changes nothing) and idempotency (GET/PUT/DELETE repeat safely, POST does not).
  • Status families: 2xx success, 3xx redirect, 4xx client error, 5xx server error — and the specific codes 200/201/204/400/401/403/404/409/422/429/500.
  • Headers carry metadata; Content-Type and Authorization are the two you will touch constantly.
  • Never assert on the status code alone — validate the body too.

Go deeper

The HTTP you have learned is the message format; a few lower-level details shape how those messages actually behave in production. Modern APIs almost always speak HTTPS — HTTP wrapped in TLS encryption — so the whole request, headers and body included, is unreadable in transit. When a call fails only over HTTPS, suspect certificates or an http→https redirect, not your payload.

Caching and conditional requests are a rich source of “why am I seeing stale data?” bugs. When a response includes an ETag, a well-behaved client can send it back on the next call as If-None-Match; if nothing changed the server answers 304 Not Modified with no body, saving bandwidth. As a tester, prove that a real update actually invalidates the cache — a 304 after something genuinely changed is a defect.

  • Compression: clients advertise Accept-Encoding: gzip and servers compress the body; most tools decompress it transparently, but a mismatch can corrupt what you assert on.
  • CORS preflight: a browser fires an automatic OPTIONS request before a cross-origin call to ask which methods and headers are allowed. A missing CORS header breaks the browser even when the API itself is fine — reproduce it with a direct request to prove where the fault lies.
  • Cookies vs tokens: session state can travel in a Cookie header (set by the server via Set-Cookie) or an Authorization: Bearer token. Know which your API uses, because it changes how you carry a session from one request to the next.

Save to list

Scroll to Top