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.
GETis safe — reading a user should never modify it. If aGETchanges 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,PUTandDELETEare idempotent: deleting user 42 twice still leaves user 42 deleted.POSTis 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 OKwith the wrong or empty body is still a bug. Always validate the body too. - Forgetting
Content-Type. Sending JSON withoutContent-Type: application/jsoncauses 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:
- 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.
- 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.
- Write the full HTTP request (method line, three headers, and a JSON body) for creating a post titled “My First Post”.
Knowledge check
- Which method partially updates a resource, and why is that different from PUT?
- Explain the difference between 401 and 403 in one sentence each.
- Which request header tells the server the body is JSON?
- Does a 500 error mean the client did something wrong? What should a tester do when they see one?
- Why is POST not idempotent, and what real bug does that cause?
Show answers
- PATCH sends only the fields to change; PUT replaces the entire resource, so fields you omit from a PUT may be erased.
- 401 = not authenticated (no/invalid credentials). 403 = authenticated but not permitted to do this action.
Content-Type: application/json.- No — a 500 is a server fault. The tester should capture the exact request that caused it and raise a defect against the backend.
- 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-TypeandAuthorizationare 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: gzipand 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
OPTIONSrequest 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
Cookieheader (set by the server viaSet-Cookie) or anAuthorization: Bearertoken. Know which your API uses, because it changes how you carry a session from one request to the next.