HTTP methods
GET, POST, PUT, PATCH and DELETE, and which of them are safe to repeat.
The method is the first word of every request. It tells the server what you want to do with the resource at the URL. The same URL, /api/Books/1sign in to see, returns a book with GET, changes it with PUT or PATCH, and removes it with DELETE. Most APIs use five methods for almost everything, and a few more show up now and then.
Methods are where the difference between a browser and a testing tool starts to matter. The address bar only sends GET. For the rest you need curl, Postman, or a Send button like the ones on this page. The examples that change shared data use curl, so the Send buttons on this page don't modify records other readers are looking at. Lesson 6, Sending requests with curl and Postman, sets up both tools properly. If you don't have curl yet, read the curl examples here and run them after that lesson.
The five everyday methods
| Method | Does | Typical success status |
|---|---|---|
GET | Read a resource or a list. Sends no body. | 200 |
POST | Create a new resource, or trigger an action. | 201, sometimes 200 or 202 |
PUT | Replace a resource with the body you send. | 200 or 204 |
PATCH | Change some fields of a resource. | 200 or 204 |
DELETE | Remove a resource. | 200 or 204 |
The Methods endpoints on this API each accept one verb and describe what they received. Send a GET:
/api/methods/get?x=1And a POST with a body:
/api/methods/post{"hello":"world"}These endpoints don't store anything. To see what methods do to real data, you need a resource, and the rest of this lesson uses Books.
POST creates
Create a book:
curl -i -X POST https://api.testingapis.com/api/Books \
-H "Content-Type: application/json" \
-d '{"title":"The Pragmatic Programmer","author":"David Thomas",
"isbn":"9780135957059","genre":"programming","publishedYear":2019,
"pages":352,"language":"English","price":42.5,"rating":4.6,
"available":true,"tags":["career"]}'HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"message": "Book created successfully",
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "9780135957059",
"genre": "programming",
"publishedYear": 2019,
"pages": 352,
"language": "English",
"price": 42.5,
"rating": 4.6,
"available": true,
"tags": ["career"]
}
}The server answered 201 Created, assigned the ID 29 and returned the new record. You will get a different ID, because other people create books too. That is the first thing to test about any POST: the server picks the ID, and your test must read it from the response rather than guess it.
Many APIs also send a Location header on a 201 with the URL of the new resource, such as Location: /api/Books/29sign in to see. This one doesn't. That isn't required by the standard, but if the docs promised it, it would be a bug.
Now run the same POST a second time. You get a second book with a new ID, identical except for that number. POST is not safe to repeat: every call makes something new. Keep that in mind for the table further down.
PUT replaces, PATCH changes
Both methods update a record, and people mix them up constantly. The difference shows up in the fields you leave out.
PATCH
Change only the price of book 29:
curl -X PATCH https://api.testingapis.com/api/Books/29 \
-H "Content-Type: application/json" \
-d '{"price":39.99}'{
"message": "Book patched successfully",
"changed": ["price"],
"ignored": [],
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "9780135957059",
"genre": "programming",
"publishedYear": 2019,
"pages": 352,
"language": "English",
"price": 39.99,
"rating": 4.6,
"available": true,
"tags": ["career"]
}
}Only the price changed. Everything you didn't mention kept its value. This API also tells you which fields it changed, which is unusual and handy for testing.
PUT
Now send a PUT with only three fields:
curl -X PUT https://api.testingapis.com/api/Books/29 \
-H "Content-Type: application/json" \
-d '{"title":"The Pragmatic Programmer","author":"David Thomas","price":39.99}'{
"message": "Book updated successfully",
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "",
"genre": "",
"publishedYear": 0,
"pages": 0,
"language": "",
"price": 39.99,
"rating": 0,
"available": false,
"tags": []
}
}The ISBN, genre, year, page count, language, rating and tags are gone. PUT means "here is the complete new version of this resource". Any field you didn't send was replaced with an empty or zero value. The API did exactly what PUT is defined to do, and it still wiped out most of a record because the client sent a partial body.
This is one of the most common real bugs in apps. A developer uses PUT to update one field, sends only that field, and every other field on the record is quietly erased. When you test an update feature, send a partial body and then GET the record to see what survived.
Some APIs handle this differently. They treat a PUT with missing fields like a PATCH, or reject it with a 400 because required fields are missing. The last option is arguably the safest. What you test for depends on the docs, but you always test it.
DELETE removes
Delete book 29, then try to delete it again:
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"message":"Book deleted successfully"} (200 OK)
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"status":404,"error":"Not Found","message":"Book with ID 29 does not exist."} (404 Not Found)The first call returns 200 with a message. The second returns 404, because there is nothing left to delete. After a delete, also check that a GET for the same ID returns 404. Some systems only mark records as deleted and keep returning them, which is a bug if the docs say otherwise.
Safe and idempotent methods
Two properties from the HTTP standard decide how clients and tests are allowed to treat each method.
A safemethod doesn't change anything on the server. You can call it as often as you like. An idempotent method can change things, but calling it twice has the same effect on the server as calling it once.
| Method | Safe | Idempotent |
|---|---|---|
GET | Yes | Yes |
HEAD | Yes | Yes |
OPTIONS | Yes | Yes |
PUT | No | Yes |
DELETE | No | Yes |
POST | No | No |
PATCH | No | Not guaranteed |
PUT is idempotent because sending the same full record twice leaves the same record. DELETE is idempotent even though the second call got a 404: the response was different, but the state of the server (book 29 doesn't exist) is the same either way. Idempotency is about the effect, not the response. POST isn't idempotent, as you saw when the second POST created a second book. PATCH can go either way. Setting the price to 39.99 is idempotent; a patch that says "add 1 to the stock count" is not.
This matters because networks fail. If a request times out, the client doesn't know whether the server received it. Retrying a GET or a PUT is harmless. Retrying a POST can charge a card twice. Browsers, proxies and HTTP libraries use these rules when they decide whether to retry on their own, and Timeouts, retries and rate limits shows how APIs make POST safe to retry with idempotency keys.
As a tester, the rules give you test cases for free. Call a GET and check that nothing changed. Call a PUT twice and check that the second call didn't create or duplicate anything. A GET that changes data (like /api/deleteUser?id=5sign in to see, which does exist in some codebases) is a design bug, because crawlers, link previews and browser prefetching all send GET requests without asking.
HEAD and OPTIONS
HEAD is a GET without the body. The server sends the same status and headers it would for a GET and stops there. Clients use it to check whether something exists or how big a file is without downloading it.
HTTP/1.1 200 OK
x-message: HEAD request received. Responses to HEAD carry headers only.
x-method: HEADOPTIONS asks what is allowed at a URL. Browsers send it automatically before some cross-origin requests (a "preflight"), which is why it shows up in network logs even when no code calls it. You will rarely test it directly unless you work on CORS settings.
/api/methods/optionsUsing the wrong method
/api/methods/get only accepts GET. Send it a POST with curl -i -X POST https://api.testingapis.com/api/methods/get:
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: GET405 Method Not Allowed means the URL exists but doesn't support that method. The allow header lists the ones it does. A POST to a single book, which makes no sense because you create books at /api/Bookssign in to see, gets the same treatment:
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: DELETE, GET, HEAD, PATCH, PUTSending every method to every endpoint is a cheap test that finds real problems. You are looking for methods that should be rejected but aren't (a DELETE that works on an endpoint that was supposed to be read-only), and for 500s or 404s where a 405 belongs. Note that this API returns the 405 with an empty body, while its other errors are JSON. Clients that expect a JSON error body on every 4xx will stumble on it.
Exercise
Work through this with curl (or come back to it after the curl lesson).
- Create a book with POST. Write down its ID.
- PATCH its title. GET it and confirm only the title changed.
- PUT it with a body that has every field except
tags. What happened to the tags? - Send the same PUT again. Did anything change the second time?
- DELETE it, GET it, and DELETE it again. Write down all three status codes.
- Send a DELETE to
/api/Bookssign in to see (no ID). Before you run it, predict the status code. Then check theallowheader.