Why this matters
Most apps you use are two programs talking. The app on your phone asks a server for your messages, sends a new message, or deletes an old one. The server is an API, short for application programming interface. It is a set of addresses and rules that another program can call. When you write code that fetches data from a service, you are calling an API.
A common style for these APIs is called REST. Its rules are simple: data is organised as things with addresses, and a small set of verbs says what to do with them. The data travels as JSON, a plain text format for structured data. This lesson shows how the pieces fit, using real requests and real responses, so you can read an API's documentation and predict what it will do.
Lesson 04 covered the raw format of a request and a response. This lesson uses that format and does not repeat it. The status codes you see here are the same ones you met before.
The idea: things with addresses and a few verbs
REST treats the server's data as resources. A resource is one thing the server keeps track of: a post, a note, a customer, an order. Each resource has a URL.
Think of a shared notebook in an office. Each page has a number, and the notebook has a shelf of pages. You can read a page, add a new page at the end, rewrite a page, or tear a page out. The notebook does not need a special command for each task. It needs one address for each page and four clear actions.
That is the whole design of a REST API:
- The URL names the thing. It is a noun, such as
/notesor/notes/1. - The method says what to do with it. It is a verb, such as GET or DELETE.
A method is the first word of an HTTP request. Lesson 04 showed GET in the first line of a request. The method, the URL and the body together describe one action.
Resources as URLs
Most REST APIs have two kinds of address for one kind of resource.
- A collection is the list. For notes, it is
/notes. - An item is one entry in the list. For note number 1, it is
/notes/1.
The number at the end is the identifier. The server picks it, usually when the item is created, and it does not change afterwards. Two notes never share the same number in one collection.
A few naming rules keep URLs predictable:
- Use plural nouns for collections:
/notes, not/note. - Do not put verbs in the URL. The action is the method. Use
DELETE /notes/1, not/notes/1/delete. - Keep the hierarchy shallow.
/notes/1is clear./users/4/notes/1/tags/9/coloursis hard to read and hard to change later.
The table below lists the URLs and methods for a notes API. The four methods in the last column cover the common actions.
| Action | Method and URL | What the server does |
|---|---|---|
| List all notes | GET /notes | Returns the collection |
| Read one note | GET /notes/1 | Returns item 1 |
| Create a note | POST /notes | Adds a new item and picks its id |
| Replace a note | PUT /notes/1 | Overwrites item 1 with the body sent |
| Delete a note | DELETE /notes/1 | Removes item 1 |
The four methods in practice
The examples in this section call a public practice API at https://jsonplaceholder.typicode.com. It has a collection called /posts. It is built for testing and it does not keep your changes, which is useful for learning. Its replies follow the usual REST rules, so the same commands work on a real service.
The commands use curl. Curl is a command-line tool that sends HTTP requests. The output below was produced by curl 8.18.0. The -w option prints the status code at the end.
GET reads
GET asks for a resource and changes nothing. It can be repeated safely. Browsers and tools rely on that, so GET must never be used to change data.
curl -s -w "\nstatus: %{http_code}\n" https://jsonplaceholder.typicode.com/posts/1{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
status: 200The body is JSON. The status is 200, which means the request worked and the reply has a body.
POST creates
POST sends data to a collection, and the server creates a new item from it. The client does not choose the id. The server replies with the new item, including its id, and the status 201 Created.
The -X POST option sets the method. The -H option adds a header that says the body is JSON. The -d option holds the body.
curl -s -w "\nstatus: %{http_code}\n" -X POST \
-H "Content-Type: application/json" \
-d '{"title": "read a book", "body": "chapter 1", "userId": 1}' \
https://jsonplaceholder.typicode.com/posts{
"title": "read a book",
"body": "chapter 1",
"userId": 1,
"id": 101
}
status: 201The server chose the id 101. The Content-Type header is the label on the body. It tells the server to read the body as JSON.
PUT replaces
PUT sends a full copy of one item to its URL, and the server overwrites the item with it. Every field the item should keep must be in the body. A field that is left out is gone after the update.
curl -s -w "\nstatus: %{http_code}\n" -X PUT \
-H "Content-Type: application/json" \
-d '{"title": "only title"}' \
https://jsonplaceholder.typicode.com/posts/1{
"title": "only title",
"id": 1
}
status: 200The reply holds only title and id. The body and userId fields from the original post are gone, because the body sent had only a title. This is the most common surprise with PUT. If you want to change one field and keep the rest, you must send the other fields too. Some APIs offer a separate method called PATCH for partial changes. It is not covered in this lesson, and you should check the API's documentation before using it.
DELETE removes
DELETE removes the item at its URL. It usually takes no body.
curl -s -o /dev/null -w "status: %{http_code}\n" -X DELETE \
https://jsonplaceholder.typicode.com/posts/1status: 200This API returns 200 with an empty object for DELETE. Other APIs return 204 No Content, which means the work succeeded and there is nothing to send back. Read the documentation for the API you use. The method says what to do. The status code says whether it worked.
JSON bodies
JSON stands for JavaScript Object Notation. Despite the name, it is used by almost every language. It is text with a small set of rules. A JSON object is a set of keys and values inside curly braces. Each key is a name in double quotes, and each value is a string, a number, true, false, null, a list or another object.
Here is one note in JSON:
{
"id": 1,
"title": "buy milk",
"done": false
}Four rules catch most beginner errors:
- Keys must be in double quotes. Single quotes are not allowed.
- Entries are separated by commas, and there is no comma after the last entry.
- Text values use double quotes too.
true,falseandnullare written in lower case.
Python can check a JSON text for you. The json.loads function reads a string and returns Python data. If the text is wrong, it raises an error that says where the problem is. Here are three broken bodies and the exact messages they produce:
JSONDecodeError: Illegal trailing comma before end of object: line 1 column 40 (char 39)
JSONDecodeError: Expecting ',' delimiter: line 1 column 15 (char 14)
JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)The message gives the line, the column and the character position where the reader got stuck. Column 15 in the second text is the "done" key, because the comma before it is missing. The position is the first place where the text stops making sense, which is not always the exact typo, so look just before it.
When a server receives a body it cannot read, it usually answers with status 400. The next section covers the status codes.
API status codes
The status code is the first thing to read in a response. It tells you whether the request worked before you look at the body. The table lists the codes that appear most often in REST APIs.
| Code | Name | Meaning in a REST API |
|---|---|---|
| 200 | OK | The request worked. A body may follow. |
| 201 | Created | A new item was made. The body usually holds it. |
| 204 | No Content | The request worked and there is no body. |
| 400 | Bad Request | The body could not be read, for example invalid JSON. |
| 401 | Unauthorized | The request has no valid login. |
| 404 | Not Found | No resource exists at this URL. |
| 422 | Unprocessable Content | The JSON is valid, but a field is missing or wrong. |
| 500 | Internal Server Error | The server failed. The request may be fine. |
The difference between 400 and 422 is a common question. A 400 means the server could not read the text at all. A 422 means the text was read, but the data inside breaks a rule, such as a missing title. Some APIs use 400 for both, and the documentation decides. Use the code as a signal and the body as the explanation. A body can name the field that is wrong.
The 404 code has a trap. A wrong URL and a missing item both return 404. In the next example, the path /post/1 is misspelled, and the server still answers with 404 and an empty object. Check the URL first, then the id.
curl -s -w "\nstatus: %{http_code}\n" https://jsonplaceholder.typicode.com/post/1{}
status: 404Worked example: filtering a collection
A collection can be long. A notes app with ten thousand notes should not send all of them to a phone that wants the notes for one folder. The usual fix is a query string: extra text after a question mark in the URL, made of name=value pairs joined by &. The server reads the pairs and returns only the matching items.
The public API has a userId field on each post. Asking for the posts of user 1 means adding ?userId=1 to the collection URL.
curl -s "https://jsonplaceholder.typicode.com/posts?userId=1" | python3 -c "import json, sys; posts = json.load(sys.stdin); print(len(posts), [p['id'] for p in posts])"10 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]The filter does not change the URL's meaning. It still names the /posts collection, and the query string narrows the reply. A user with no posts gets an empty list, not a 404. The collection exists, and it has no matching items. Asking for ?userId=99 returns zero items with status 200.
This is a useful habit when reading an API's documentation. Look for the fields that can be filtered, and check how the API replies when nothing matches. Some APIs return an empty list, and others return 404 for a missing parent item, such as a user that does not exist. The documentation tells you which.
Worked example: a small Python client
Calling an API from code uses the same steps as curl. Send a method, a URL and optionally a JSON body. Read the status. Parse the body. The client below does those steps for four calls. The call helper turns a Python value into JSON for the body, and turns the reply back into Python data.
import json
from urllib.error import HTTPError
from urllib.request import Request, urlopen
base = "https://jsonplaceholder.typicode.com"
def call(method, path, body=None):
data = None
headers = {"Accept": "application/json"}
if body is not None:
data = json.dumps(body).encode()
headers["Content-Type"] = "application/json"
req = Request(base + path, data=data, method=method, headers=headers)
with urlopen(req) as resp:
return resp.status, json.loads(resp.read() or b"null")
status, post = call("POST", "/posts", {"title": "read a book", "body": "chapter 1", "userId": 1})
print("POST", status, post)
status, post = call("GET", "/posts/1")
print("GET ", status, post["title"])
status, data = call("DELETE", "/posts/1")
print("DELETE", status, data)
try:
call("GET", "/posts/999")
except HTTPError as e:
print("GET /posts/999 raised", type(e).__name__, "-", e)POST 201 {'title': 'read a book', 'body': 'chapter 1', 'userId': 1, 'id': 101}
GET 200 sunt aut facere repellat provident occaecati excepturi optio reprehenderit
DELETE 200 {}
GET /posts/999 raised HTTPError - HTTP Error 404: Not FoundThree details matter in this code. First, urlopen raises an error for a 4xx or 5xx status, so a 404 does not come back as a normal return. The try block catches it. Second, the line resp.read() or b"null" handles an empty body, such as one from a DELETE. Third, the client reads the status code and the body separately, because a non-error reply can still hold a problem the caller must check.
Worked example: a harder flow with a changed record
Suppose a note app shows a list of notes, lets the user edit one, and then deletes another. Each step maps to one method.
GET /notesloads the list. The app shows the titles.GET /notes/1loads one note before editing it.PUT /notes/1sends the whole note with the new title. The app must includedoneandid, or the saved note loses them.DELETE /notes/2removes a note the user chose.GET /notes/2should now return 404. If it returns 200, the delete did not happen.
Step 3 is the one that causes bugs in practice. An app that sends only the changed title will save a note without its other fields. The fix is to load the whole item, change the field in code, and send the full object back. The PUT example earlier shows exactly what happens when the fields are missing.
Step 5 teaches a habit. Check the outcome with a read after a write when the result matters. The status code of the write is a claim from the server. The read shows the state that other clients will see.
Common mistakes
A verb in the URL. POST /notes/1/delete works on some servers, but it breaks the rules. The method already says delete. Use DELETE /notes/1. A clear URL is also easier to find in the documentation.
GET used to change data. A link in a browser or a search engine may request a GET URL again. If that GET deletes an item, the item is deleted again. Changes belong to POST, PUT, PATCH or DELETE.
Forgetting the Content-Type header. Without the header, a server may not know the body is JSON, and it can reject a valid body or read it wrongly. Add Content-Type: application/json whenever you send a JSON body.
Invalid JSON in the body. A body with a trailing comma fails with an error such as JSONDecodeError: Illegal trailing comma before end of object. The server returns 400, and the change is not made. Test the body with json.loads before sending it.
Partial PUT. As the PUT example showed, a PUT with only a title removes the other fields. Read the item, change the field, and send the full item.
Trusting a practice API to keep changes. The practice API replies as if the change was saved, but a later GET of the same item shows the original data. Use it to learn the request shapes. Do not expect it to store anything.
Reading only the status code. A 422 or 500 response has a body with the reason. Print it when a call fails, or the error turns into a guess.
Practice
Exercise 1. Give the method and URL for each action in a notes API: create a note, delete note 7, and read note 7.
Show the answer
Create a note: POST /notes. Delete note 7: DELETE /notes/7. Read note 7: GET /notes/7.
Exercise 2. The client sends POST /notes with a body that is valid JSON, but the title is missing. Which status code fits best, and why is 400 less exact here?
Show the answer
422 Unprocessable Content fits best, because the JSON was read and the data breaks a rule. 400 means the body could not be read at all. Some APIs use 400 for both, so check the documentation.
Exercise 3. A client calls POST /posts/1/delete to remove post 1. What is wrong with this URL, and what should it be?
Show the answer
The URL contains a verb, delete. The method already says what to do. The correct request is DELETE /posts/1.
Exercise 4. Is this a valid JSON body? {"id": 2, "title": "walk", "done": true,} If not, what goes wrong?
Show the answer
It is not valid. The comma after true is a trailing comma, and JSON does not allow one before the closing brace. Python reports JSONDecodeError: Illegal trailing comma before end of object. Remove the comma.
Exercise 5. A note is {"id": 1, "title": "buy milk", "done": false}. The client sends PUT /notes/1 with the body {"title": "buy bread", "done": false}. What is the saved note, and what is the problem?
Show the answer
The title changes to "buy bread" and done stays false, but the id is missing from the body. Whether the saved note keeps its id depends on the server. The safe fix is to send the full item, including id, and then run a GET to see what was stored.
Summary
- A REST API names data with URLs, where collections are plural nouns and items add an id.
- The method says the action: GET reads, POST creates, PUT replaces the whole item, and DELETE removes it.
- Bodies are JSON. Set
Content-Type: application/jsonand check the text is valid. - The status code is the first signal: 200 and 201 mean success, 400 and 422 mean a bad request, 404 means no item at that URL, and 500 means the server failed.
- Read the body on errors, and read the item back after a change that matters.