Lesson 1 of 10 · Hands-on Lab · 60 min · Free preview

HTTP & REST Fundamentals — Methods, Status Codes, Headers & a curl Lab

Learn how HTTP requests and responses really work, why idempotency matters, and practise full CRUD on Restful Booker using only curl and jq.

Why this matters on the job

Open any QA job post on Naukri or LinkedIn for a 2–6 year role and you will see the same line: "Hands-on experience in API testing using Postman / REST Assured." The reason is simple. A modern web app, its Android app and its iOS app all talk to the same backend APIs. If the /orders API returns the wrong total, every channel is broken — and you can catch that bug in milliseconds at the API layer instead of waiting for a slow, flaky UI test.

In real projects, testers who can read a raw HTTP request, reproduce a bug with a single curl command and paste it into the Jira ticket are taken far more seriously by developers. "Login is not working" gets ignored; "POST /auth returns 200 with {"reason":"Bad credentials"} instead of 401 — curl attached" gets fixed. This lesson gives you that vocabulary and that skill.

Concepts

Anatomy of an HTTP request and response

Every API call is a request from a client and a response from a server. A request has a method (GET, POST…), a URL (scheme + host + path + optional query string), headers (metadata such as Content-Type) and an optional body. A response has a status line (e.g. HTTP/1.1 200 OK), headers, and usually a body (JSON in most REST APIs).

POST /booking HTTP/1.1                      <- method + path + protocol
Host: restful-booker.herokuapp.com          <- headers start
Content-Type: application/json
Accept: application/json

{"firstname":"Aarav","lastname":"Sharma"}   <- body

HTTP/1.1 200 OK                             <- status line
Content-Type: application/json; charset=utf-8

{"bookingid":1234,"booking":{...}}          <- response body

REST (Representational State Transfer) is an architectural style, not a protocol. Its key ideas: everything is a resource with a URL (/booking/12), you act on resources with standard HTTP methods, every request is stateless (the server keeps no session between calls — auth travels with each request), and responses are representations such as JSON.

HTTP methods, safety and idempotency

A method is safe if it does not change server state. It is idempotent if sending the same request once or ten times leaves the server in the same state. Testers care because retries happen constantly — mobile networks drop, load balancers retry, users double-click "Pay".

MethodPurposeSafeIdempotentTypical success code
GETRead a resource or listYesYes200 OK
POSTCreate a resource / trigger an actionNoNo201 Created (+ Location header)
PUTReplace the whole resourceNoYes200 OK or 204 No Content
PATCHUpdate some fieldsNoNot guaranteed200 OK
DELETERemove a resourceNoYes204 No Content or 200 OK
HEAD / OPTIONSHeaders only / allowed methods (CORS preflight)YesYes200 / 204

Payment gateways solve the "POST is not idempotent" problem with an Idempotency-Key header: the client sends a unique key, and if the same key arrives twice the server returns the first result instead of charging the customer twice. "How would you test a double-click on Pay?" is a favourite product-company interview question — the answer starts here.

Status codes every tester must know

CodeMeaningWhen you typically see it
200 OKSuccess with bodyGET, PUT, PATCH
201 CreatedResource createdPOST (should include Location)
204 No ContentSuccess, empty bodyDELETE, some PUTs
301 / 302 / 304Moved / Found / Not ModifiedRedirects, caching with ETag
400 Bad RequestMalformed or invalid inputMissing field, wrong type
401 UnauthorizedNot authenticated (who are you?)Missing/expired token
403 ForbiddenAuthenticated but not allowedUser A editing User B's data
404 Not FoundResource doesn't existWrong id or path
405 Method Not AllowedMethod not supported on this pathDELETE on a read-only endpoint
409 ConflictState conflictDuplicate email on sign-up
415 Unsupported Media TypeWrong Content-TypeSending XML to a JSON API
422 Unprocessable EntityValid JSON, failed business rulesCheckout date before check-in
429 Too Many RequestsRate limitedOTP API hammered
500 / 502 / 503 / 504Server error / bad gateway / unavailable / timeoutAlways a bug or infra issue to report

Headers you will use daily

HeaderDirectionWhy testers care
Content-TypeRequest & responseFormat of the body being sent/returned
AcceptRequestFormat the client wants back
AuthorizationRequestBasic / Bearer credentials
Cookie / Set-CookieBothSession or token cookies (restful-booker uses Cookie: token=…)
Cache-Control, ETagResponseStale data bugs, 304 behaviour
LocationResponseURL of a newly created resource
X-Request-Id / correlation idBothPaste in bug reports so devs can find logs

Hands-on Lab: curl lab on Restful Booker

Target: Restful Booker (public practice API — data resets periodically, other learners use it too, so always create your own data). Docs: restful-booker API docs. Keep a notes table with columns Request, Expected, Actual, Observation.

  1. Verify tools. On Windows use Git Bash (CMD/PowerShell quoting differs). Install jq: brew install jq (macOS), sudo apt install jq (Ubuntu) or winget install jqlang.jq (Windows).
    curl --version
    jq --version
  2. Health check — look at the status line and headers (-i includes headers). Expected: HTTP/1.1 201 Created. Note it: a health check returning 201 is a quirk, most APIs return 200.
    curl -i https://restful-booker.herokuapp.com/ping
  3. List bookings and show only the first three with jq. Then filter with a query parameter.
    curl -s https://restful-booker.herokuapp.com/booking | jq '.[0:3]'
    curl -s "https://restful-booker.herokuapp.com/booking?firstname=Aarav" | jq
  4. Create a booking (POST) and capture the id into a shell variable. Expected: JSON with bookingid and the echoed booking. Check the status code: it is 200, while REST convention says 201 — log it as an observation.
    BASE=https://restful-booker.herokuapp.com
    BODY='{"firstname":"Aarav","lastname":"Sharma","totalprice":1500,"depositpaid":true,"bookingdates":{"checkin":"2026-12-01","checkout":"2026-12-05"},"additionalneeds":"Breakfast"}'
    
    curl -s -i -X POST "$BASE/booking" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json" \
      -d "$BODY"
    
    BOOKING_ID=$(curl -s -X POST "$BASE/booking" \
      -H "Content-Type: application/json" -H "Accept: application/json" \
      -d "$BODY" | jq -r '.bookingid')
    echo "Created booking: $BOOKING_ID"
  5. Prove POST is not idempotent: run the second command above twice and confirm you get two different ids.
  6. Get an auth token (credentials are public on the restful-booker docs page).
    TOKEN=$(curl -s -X POST "$BASE/auth" \
      -H "Content-Type: application/json" \
      -d '{"username":"admin","password":"password123"}' | jq -r '.token')
    echo "$TOKEN"
  7. Prove PUT is idempotent: send the same full update twice. Both responses must be identical and a GET afterwards must show the same state.
    UPDATE='{"firstname":"Aarav","lastname":"Sharma","totalprice":2000,"depositpaid":false,"bookingdates":{"checkin":"2026-12-02","checkout":"2026-12-06"},"additionalneeds":"Lunch"}'
    for i in 1 2; do
      curl -s -X PUT "$BASE/booking/$BOOKING_ID" \
        -H "Content-Type: application/json" -H "Accept: application/json" \
        -H "Cookie: token=$TOKEN" -d "$UPDATE"; echo
    done
    curl -s -H "Accept: application/json" "$BASE/booking/$BOOKING_ID" | jq
  8. PATCH only one field and confirm the rest is unchanged.
    curl -s -X PATCH "$BASE/booking/$BOOKING_ID" \
      -H "Content-Type: application/json" -H "Accept: application/json" \
      -H "Cookie: token=$TOKEN" -d '{"additionalneeds":"Airport pickup"}' | jq
  9. Negative checks. Expected: PUT without token → 403; unknown id → 404. -w prints just the status and time.
    curl -s -o /dev/null -w "no-token PUT => %{http_code}\n" -X PUT "$BASE/booking/$BOOKING_ID" \
      -H "Content-Type: application/json" -d "$UPDATE"
    curl -s -o /dev/null -w "unknown id => %{http_code} in %{time_total}s\n" \
      -H "Accept: application/json" "$BASE/booking/999999999"
  10. DELETE and verify. Expected: DELETE returns 201 Created (another quirk — 204/200 is conventional), then GET returns 404.
    curl -s -o /dev/null -w "delete => %{http_code}\n" -X DELETE "$BASE/booking/$BOOKING_ID" -H "Cookie: token=$TOKEN"
    curl -s -o /dev/null -w "get after delete => %{http_code}\n" "$BASE/booking/$BOOKING_ID"
  11. Run one call with -v and identify: the TLS handshake, the request headers curl sent (lines starting with >) and the response headers (<). Note the X-Powered-By header — we will revisit it in the security lesson.

By the end you should have recorded at least three convention deviations: /ping returns 201, POST /booking returns 200 instead of 201, and DELETE returns 201. In a real project each of these becomes a conversation with the developer or a documented "known behaviour" — never something you silently accept.

Common mistakes

  • Forgetting Content-Type: application/json on POST/PUT — the server cannot parse the body and you report a false bug.
  • Copying Linux curl commands into Windows CMD; single quotes don't work there. Use Git Bash or Postman.
  • Checking only the status code. A 200 with an error message in the body (restful-booker's bad-credentials response) is still a failure.
  • Mixing up 401 and 403: 401 = not logged in, 403 = logged in but not permitted.
  • Using shared/production data. Always create the record you test, then clean it up.

Real-world assignment

Your lead shares the JSONPlaceholder API and asks for a "curl smoke pack" before the sprint demo. Deliver a smoke.sh script with 10 curl checks covering GET /posts, GET /posts/1, GET /posts/1/comments, GET /comments?postId=1, POST /posts (expect 201 and id: 101), PUT, PATCH, DELETE /posts/1, GET /posts/0 (expect 404) and GET /users/1. Each line must print name, expected code, actual code, PASS/FAIL. Add one sample bug report in the format Title / Environment / curl / Expected / Actual / Evidence.

Key takeaways

  • Every API call = method + URL + headers + body; every response = status + headers + body.
  • GET, PUT and DELETE are idempotent; POST is not — that is why retries and double-clicks are a testing priority.
  • Assert status code and body and important headers.
  • curl + jq is the fastest way to reproduce and share an API bug.
  • Document convention deviations (200 vs 201, 201 on DELETE) instead of ignoring them.

🧪 Practical checklist

Do each task yourself and tick it off. All tasks are required to complete this lesson.

🎤 Interview questions

What is the difference between PUT and PATCH?

PUT replaces the entire resource with the payload you send and is idempotent; PATCH sends only the fields to change. Missing fields in a PUT may be reset or rejected, which is a classic test case.

Explain idempotency with an example and how you would test it.

An operation is idempotent if repeating it leaves the server in the same state, e.g. PUT /booking/5 with the same body. I test by sending the request multiple times and verifying the resource state and side effects (no duplicate records or charges).

When should an API return 401 vs 403?

401 when the caller is not authenticated (missing, invalid or expired credentials); 403 when the caller is authenticated but lacks permission for that resource or action.

An API returns 200 but the body contains an error message. Is that a pass?

No. The status code should reflect the outcome; a 200 with an error body breaks clients that rely on status codes. I would fail the check and raise it as a defect or contract issue.

📝 Quiz, progress tracking & certificate

You're reading a free preview. Premium members tick off labs, take the quiz, unlock all 10 lessons and earn a verifiable certificate.

💎 Unlock with Premium Premium login
✓ You're subscribed! Job alerts arrive daily at 9 AM.
Scroll to Top