REST API Basics
REST (Representational State Transfer) is an architectural style described by Roy Fielding in his 2000 doctoral dissertation: a set of constraints, not a protocol or a standard. A REST API exposes resources — an order, a customer, a task — each identified by a URI. Clients act on them with the standard HTTP methods and exchange representations of them, usually as JSON.
Example: a to-do API exposes the collection /tasks and each task at its own URI, such as /tasks/42. GET /tasks/42 returns task 42, PATCH /tasks/42 with {"done": true} changes one field, and DELETE /tasks/42 removes it. The URI names the thing, the method says what to do with it, and the status code says how it went.
Why it matters
REST conventions let you predict the expected behaviour before you send a request: a create returns 201 Created, a deleted resource is no longer found, an unsupported method is refused with 405. Wherever an API breaks a convention without documenting it, client developers are likely to write code that handles it wrongly.
The cost of an API defect is multiplied by its clients. A renamed field or a 500 for bad input can break a web front end, a partner integration and app versions already installed on users' phones, none of which changed. Knowing the conventions turns "the API looks odd" into precise expectations for your test cases.
Resources, URIs, representations and statelessness
Fielding defines REST through six constraints, one of them optional, including statelessness and a uniform interface. APIs called REST often apply them only partly — hypermedia links that tell the client what it can do next are often left out — so judge an API by its own contract first and use REST conventions to spot inconsistencies. Four ideas matter most for testing:
- Resource: a named thing the API exposes — a task, a user, the list of tasks. Resources are nouns and the action comes from the method:
POST /tasks, notPOST /createTask. - URI: the resource's identifier, with the syntax defined in RFC 3986. A common convention is a plural collection
/tasks, its members/tasks/42and sub-resources/tasks/42/comments. - Representation: the resource's state in a concrete format. The client names the format it sends in
Content-Typeand the formats it can read inAccept. - Statelessness: each request carries everything the server needs to understand it — credentials such as a token, identifiers, the page number. The server keeps no session context from earlier requests (the stored resources themselves of course remain), so a request can be repeated, sent on its own or handled by another server and still mean the same thing. Test it that way: request page 3 directly, without fetching pages 1 and 2 first.
Methods mapped to CRUD
CRUD — create, read, update, delete — names the basic operations on stored data. In REST each one is an HTTP method applied to the collection or to one item. You met the methods and their safe and idempotent properties in the lesson on HTTP methods; the table combines them with resources.
The pattern behind the table: you create on the collection, and the server chooses the new item's URI; you read the collection or one item; you update and delete one item at its own URI. Combinations outside the table — POST /tasks/42, or PUT, PATCH and DELETE on the whole collection — are usually not offered and should get 405 Method Not Allowed.
| Method | Resource | |
|---|---|---|
/tasksthe collection | /tasks/{id}one item | |
GETRead | List the tasks200 OK, often paged | Read one task200 OK · 404 if it does not exist |
POSTCreate | Create a task; the server picks the id201 Created + Location | Usually not offered405 + Allow |
PUTUpdate: replace | Usually not offered405 + Allow | Replace the whole task200 or 204 · 201 if it created the task |
PATCHUpdate: some fields | Usually not offered405 + Allow | Change only the fields sent200 or 204 |
DELETEDelete | Usually not offered405 + Allow | Delete the task204 (or 200, 202) · then GET gives 404 |
- CRUD operation and its typical success code
- Not offered: expect
405 Method Not Allowedwith anAllowheader
A typical design: the API's own contract decides which combinations exist.
Text version
Operation Method URI Typical success response
--------- ------ ----------- ---------------------------------------------
Create POST /tasks 201 Created + Location: /tasks/{id}
Read GET /tasks 200 OK, a list (often paged)
Read GET /tasks/{id} 200 OK, one task
Update PUT /tasks/{id} 200 OK or 204 No Content, whole task replaced
Update PATCH /tasks/{id} 200 OK or 204 No Content, sent fields only
Delete DELETE /tasks/{id} 204 No Content (200 with a body, 202 if queued)Status codes in a REST API
Clients branch on the status code before they read the body, so the code is part of the contract. In REST it follows from the operation and the state of the resource, so every test case can state the expected code.
201 Createdafter a create, with aLocationheader holding the new URI (RFC 9110). A200with the id only in the body is a common deviation; the contract decides whether it is a defect.204 No Contentfor a successful update or delete with nothing to return: the body must be empty.404 Not Foundfor a missing item, including one just deleted. An empty list is200with an empty array.405 Method Not Allowedfor a method the resource does not support, with anAllowheader listing the ones it does; RFC 9110 requires that header.415 Unsupported Media Typefor a body format the resource does not accept;406 Not Acceptablewhen the server cannot produce any format listed inAccept— RFC 9110 also lets it send its default format instead.409 Conflictwhen the request clashes with the current state, such as an e-mail that is already registered;400or422for invalid input, used consistently.- Never
5xxfor a client mistake: a500for bad input usually means input validation or error handling is missing.
Try it: HTTP Status Analyzer Try it: HTTP Status Code Lookup
Worked example: one task through its lifecycle
A tester walks one task through every operation, asserting the status code and the stored state after each step. The test account starts with no tasks.
- Later steps use
/v1/tasks/57from theLocationheader instead of a guessed URI. - POST belongs to the collection: on the item
/v1/tasks/57it gets405, andAllowlists the methods the item does support. - After the DELETE the item is gone, so
GET /v1/tasks/57returns404; the collection still exists, soGET /v1/tasksreturns200with an empty array, not404. - Each method also keeps its own promises here — PATCH changes only the fields sent, PUT carries the complete state, and a repeated DELETE may return
404and still be idempotent — as the sections on safe and idempotent methods and on what to test for each method in the lesson on HTTP methods explain.
POST /v1/tasks {"title": "Pay rent", "done": false}
-> 201 Created, Location: /v1/tasks/57
GET /v1/tasks/57
-> 200 OK, {"id": 57, "title": "Pay rent", "done": false}
PATCH /v1/tasks/57 {"done": true}
-> 200 OK, {"id": 57, "title": "Pay rent", "done": true}
POST /v1/tasks/57
-> 405 Method Not Allowed, Allow: GET, HEAD, PUT, PATCH, DELETE
DELETE /v1/tasks/57
-> 204 No Content
GET /v1/tasks/57
-> 404 Not Found
GET /v1/tasks
-> 200 OK, []Versioning and breaking changes
An API has clients its team does not control, so it cannot change freely. A breaking change makes a correct, unchanged client fail: removing or renaming a field, changing its type, making an optional request field required, or changing a URI or a status code. Adding an endpoint or an optional field is usually safe, provided clients ignore fields they do not know.
REST prescribes no versioning scheme. Teams choose one and document it in the contract, often an OpenAPI document: the URI path (/v1/tasks), a query parameter (?version=2), a custom request header such as Api-Version: 2 (the name varies by API) or the media type (Accept: application/vnd.example.v2+json).
- After a new version ships, run the old version's regression tests unchanged:
v1must still behave as its contract says. - Send the same request to both versions and confirm that only the documented differences appear.
- Send no version, an unknown version and a retired one: expect the documented default or a clear
4xx, never a500.
Common mistakes
- Treating REST as a standard with fixed rules. It is an architectural style; the API's contract sets the details.
- Testing requests only in the order the user interface sends them. A REST request is stateless, so send it on its own too: page 3 without pages 1 and 2, an item by its URI without loading the list.
- Accepting any
2xxas a pass when clients depend on the exact code, such as201withLocation. - Expecting
404for an empty list. The collection still exists, so the correct answer is200with an empty array;404is for a missing item. - Testing only the newest version after a release and missing a change that leaked into
v1.
The QA perspective
REST knowledge pays off before any code runs. Review a new or changed contract like a requirement: for each resource, which methods exist, which status code each returns, what happens on a duplicate or a missing item, and whether a change is breaking. Agree the answers with the developers and the teams that consume the API — web, mobile, partners.
Write the expected status code and key headers into every test case and keep one lifecycle test per resource. In a bug report give the method, URI, request body, expected and actual status, so a developer can replay the call in seconds.
Practice exercise
Review a clinic booking API transcript
You are testing the appointments resource of a clinic booking API on staging. The contract defines /v1/appointments and /v1/appointments/{id}, documents 201 Created with a Location header for a create and a date filter on the list, and returns 404 for missing items. Decide, line by line, whether each observed response is correct; for each wrong one, state the expected response and why.
# Request Observed response
1 POST /v1/appointments (valid JSON body) 200 OK, body has "id": 381,
no Location header
2 GET /v1/appointments?date=2026-11-02 404 Not Found,
no appointments on that date
3 POST /v1/appointments/381 404 Not Found
4 POST /v1/appointments Content-Type: text/plain 500 Internal Server Error
5 DELETE /v1/appointments/381 204 No Content
6 GET /v1/appointments/381 404 Not Found
7 DELETE /v1/appointments/381 500 Internal Server Error
8 GET /v1/appointments/377 (after v2 release) "time" is now named "startsAt"- Write the expected status code (and header, where one matters) from the method, the URI and the state of the resource at that moment.
- Compare it with the observed response and mark the line as pass or defect.
- Order the defects by their impact on clients and users.
Show the model answer
Correct: lines 5 and 6. After the DELETE, 404 is exactly what should happen.
Line 1: expected 201 Created with Location: /v1/appointments/381, as the contract documents. Clients that read the new URI from Location cannot continue.
Line 2: the collection exists even when no appointment matches the filter, so expected 200 OK with an empty array. A 404 tells clients the list itself is missing, and many will show an error instead of an empty day.
Line 3: POST belongs to the collection /v1/appointments, not to an item, and appointment 381 exists, so 404 is misleading; expected 405 Method Not Allowed with an Allow header listing the methods the item supports.
Line 4: an unsupported body format is a client error; expected 415 Unsupported Media Type, not a crash.
Line 7: the appointment is already deleted, so expected 404, as the contract says for missing items. The 500 is an unhandled case; confirm with a GET that the state did not change.
Line 8: a breaking change leaked into v1. The rename belongs only in v2.
Raise first: line 8 (breaks existing clients); then lines 4 and 7 (500 where a 4xx is expected); then lines 1, 2 and 3 (misleading codes).
Frequently asked questions
Is REST the same as HTTP?
No. HTTP is a protocol, defined in IETF standards such as RFC 9110. REST is an architectural style. REST APIs normally use HTTP, but an HTTP API can ignore REST entirely, for example by sending every action as a POST to a single URL.
Should a create return 200 or 201?
RFC 9110 says that when POST creates a resource, the server should send 201 Created with a Location header identifying it. Some APIs return 200 with the new id in the body; that works only if the contract says so and every client expects it. Test against the contract, and raise it when endpoints of the same API do it differently.
How does a REST API split a long list into pages?
REST prescribes no scheme, so the contract must state one. Common designs are page or offset parameters (?page=3&size=20) and an opaque cursor that each response hands back for the next request; some APIs also return the URI of the next page, in the body or in a Link header. Test the first and last page, a page past the end (200 with an empty array, or the documented 4xx), the largest allowed page size and one above it, and that no item is skipped or repeated when items are added between requests, a known weakness of offset paging.
Which versioning scheme is best?
There is no standard answer. A version in the URI path is easy to see, route and test; header and media-type versioning keep URIs stable. For testing, what matters is that the scheme is documented, applied the same way on every endpoint, and that old versions keep working until their announced retirement.
References
- RFC 9110: HTTP SemanticsThe meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
- RFC 3986: Uniform Resource Identifier (URI): Generic SyntaxThe parts of a URI (scheme, authority, path, query, fragment), reserved characters and percent-encoding.
- RFC 5789: PATCH Method for HTTPThe PATCH method for partial updates, as opposed to PUT replacing a whole resource.
- OpenAPI SpecificationThe standard for describing REST APIs: paths, operations, parameters, schemas, responses and security schemes.
Practise with Mutqan tools
Test REST endpoints with full control over method, headers and JSON body, then assert on status, headers and JSON paths.
Compose and send HTTP requests (method, URL, headers, auth, body) and inspect the response — a lightweight Postman in your browser.
Analyze the HTTP status code and headers of a response and get an explanation of whether it is correct for the situation.
Look up any HTTP status code with its meaning, RFC reference and when to use it.
Compare two API responses (e.g. staging vs production) and list every structural and value difference.
Render an OpenAPI 3.x / Swagger 2.0 spec (JSON or YAML) as browsable endpoint documentation.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.