Consume a Public REST API
Make real HTTP requests against a live REST API with curl: read status lines and headers, GET with query params, POST/PUT/DELETE, and provoke real error codes. The hands-on companion to HTTP & REST Essentials.
Problem
Every app you'll ever build talks to APIs over HTTP. Reading about verbs and status codes is one thing; *seeing* a `201 Created` come back from a `POST` you wrote, with the headers that made it work, is what makes the protocol click. In this lab you'll hit a real, public REST API from your terminal with `curl`. You'll inspect the full response — status line, headers, body — send data with `POST`, `PUT` and `DELETE`, and deliberately trigger `400` and `404` to read what a failing API tells you. No app to build, no keys to manage: just you, the protocol, and a real server answering back. You'll finish able to explore and debug any HTTP API you meet.
Objectives
- Make a request with
curland read the full response: status line, headers and body - Use
-i,-vand-oto control what part of the response you see - GET a collection and a single resource, and pass query parameters
- Send a JSON body with
POSTandPUT, setting theContent-Typeheader correctly - Delete a resource with
DELETEand read the resulting status code - Provoke and interpret real error responses (
400,404) and the meaning of2xx/4xx/5xx
Prerequisites
-
curlinstalled (bundled on macOS, most Linux, and modern Windows — runcurl --version) - Internet access to reach a public test API
- Optional but nice: a JSON pretty-printer (
jq) to read bodies; the lab works without it - The concepts from HTTP & REST Essentials: methods, status codes, headers, request/response
- Git installed, to submit your request log at the end
What you will build
Not an app — fluency with the protocol. You'll run a sequence of curl requests against a
public test API and collect the results into a requests.md log. By the end you'll have exercised
every core HTTP verb and read real success and error responses.
The API you'll use
You'll use JSONPlaceholder (https://jsonplaceholder.typicode.com), a free fake REST API for
testing. It exposes standard resources — /posts, /users, /comments — and accepts writes
(POST/PUT/DELETE) that it simulates: it returns a realistic response as if it saved your
data, without actually persisting it. That's perfect for learning the request/response shape safely.
The mental model: request → response
Every HTTP exchange has the same skeleton, and curl lets you see all of it:
| Part | Request | Response |
|---|---|---|
| Start line | GET /posts/1 HTTP/1.1 |
HTTP/1.1 200 OK |
| Headers | Accept: application/json |
Content-Type: application/json |
| Body | (JSON you send on POST/PUT) | (JSON the server returns) |
The status code on the response start line is the server's one-word verdict: 2xx success,
3xx redirect, 4xx you got it wrong, 5xx the server got it wrong. Learning to read it first is
the fastest way to debug any API call.
How to work through this lab
Run each request yourself and read the real output. Save the command and a note of what came back
into requests.md as you go — that's your Step 6 deliverable.
Steps
-
Your first request: read the full response
Start by seeing every part of an HTTP response, not just the body.
A plain GET
curl https://jsonplaceholder.typicode.com/posts/1You get a JSON object — a single "post" with
userId,id,title,body. Butcurlhid the
status and headers by default. Let's reveal them.See the headers with -i
curl -i https://jsonplaceholder.typicode.com/posts/1-iincludes the response headers above the body. Read the top:HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 ...-
HTTP/1.1 200 OKis the status line — protocol version, status code, reason phrase. -
Content-Type: application/jsontells you the body is JSON, so you know how to parse it.
See everything with -v
curl -v https://jsonplaceholder.typicode.com/posts/1-v(verbose) shows the request too. Lines starting with>are what curl sent; lines
with<are what the server returned. This is your single most useful debugging flag — when a
call misbehaves,-vshows the exact request and response on the wire.Deliverable for this step: the status line and
Content-Typeheader from the-irequest. -
-
GET a collection and use query parameters
A single resource is
/posts/1. The whole collection is/posts. Query params filter it.Get the collection
curl -s https://jsonplaceholder.typicode.com/posts | head -c 300-
-s(silent) hides curl's progress meter, giving clean output for piping. -
head -c 300shows just the first 300 characters so a 100-item array doesn't flood your terminal.
You get a JSON array of posts. Collections return arrays; single resources return objects —
a REST convention worth internalizing.Filter with query parameters
REST APIs let you filter a collection via
?key=valueon the URL:curl -s "https://jsonplaceholder.typicode.com/posts?userId=1"This returns only posts where
userIdis 1. Note the quotes around the URL — the?and&
are special in most shells, and quoting stops the shell from mangling them. Add more params with&:curl -s "https://jsonplaceholder.typicode.com/comments?postId=1&_limit=2"-
postId=1filters comments to post 1;_limit=2caps the result at 2 items. - Everything after
?is the query string — it modifies how you read the resource, without changing the path.
Pretty-print (optional)
If you have
jq, pipe into it for readable JSON:curl -s "https://jsonplaceholder.typicode.com/posts/1" | jqDeliverable for this step: the filtered result of the
?userId=1request (or its first item). -
-
Send data: POST and PUT with a JSON body
Reading is
GET. To create and update, you send a body withPOSTandPUT.Create with POST
curl -i -X POST https://jsonplaceholder.typicode.com/posts \ -H "Content-Type: application/json" \ -d '{"title": "My first post", "body": "Hello REST", "userId": 1}'Every flag matters:
-
-X POSTsets the method to POST (create). -
-H "Content-Type: application/json"declares that the body you're sending is JSON. Omit
this and the server may not parse your data — a top cause of "why is my POST empty?". -
-d '{...}'is the request body, the data you're creating.
Read the response with
-i: the status is201 Created(not 200), and the body echoes your
object with a new"id": 101.201is the correct success code for "a new resource was created" —
distinct from200 OKfor a plain read.Update with PUT
curl -i -X PUT https://jsonplaceholder.typicode.com/posts/1 \ -H "Content-Type: application/json" \ -d '{"id": 1, "title": "Updated title", "body": "New body", "userId": 1}'-
PUT /posts/1replaces the resource at that id with the body you send. - The status is
200 OKand the body reflects your update. - Semantics to remember:
POSTto a collection creates;PUTto a specific id replaces.
(JSONPlaceholder simulates these — it returns the correct response without truly saving. The
request/response mechanics are exactly what a real API expects.)Deliverable for this step: the
201 Createdstatus line and the returnedidfrom the POST. -
-
DELETE and reading status codes on purpose
The last core verb, and then a tour of what failure looks like.
Delete a resource
curl -i -X DELETE https://jsonplaceholder.typicode.com/posts/1DELETE /posts/1removes the resource. The response is200 OK(some APIs use204 No Content,
meaning "done, nothing to return"). Notice the body is empty{}— a delete confirms via the
status code, not the body. This is why reading status first matters.Get just the status code
To capture only the code (handy in scripts), use
-o /dev/null -w:curl -s -o /dev/null -w "%{http_code}\n" https://jsonplaceholder.typicode.com/posts/1-
-o /dev/nullthrows the body away. -
-w "%{http_code}\n"writes just the numeric status. You'll see200.
Provoke real errors
Now break things on purpose and read what the server says:
# 404 Not Found — the resource doesn't exist curl -s -o /dev/null -w "%{http_code}\n" https://jsonplaceholder.typicode.com/posts/99999 # 404 on a bad path curl -i https://jsonplaceholder.typicode.com/nonsenseThe status-code families, which you should be able to recite:
Range Meaning Example 2xxSuccess 200 OK,201 Created,204 No Content3xxRedirect 301 Moved Permanently4xxYour request was wrong 400 Bad Request,401 Unauthorized,404 Not Found5xxServer failed 500 Internal Server Error,503 Service UnavailableWhen a call fails, the code tells you whose fault it is —
4xxmeans fix your request,5xx
means the server broke. That single distinction saves hours of debugging.Deliverable for this step: the status codes you captured for the DELETE, the valid GET, and the 99999 (not found) request.
-
-
Headers that matter: Accept, Content-Type, and auth
Headers are the metadata that make a request work. Three you'll use constantly.
Content-Type — what you are sending
You already used it on POST.
Content-Typedescribes the body you send so the server parses
it correctly. Send JSON →application/json. Send a form →application/x-www-form-urlencoded.
Mismatch it and the server may reject or misread your data.Accept — what you want back
Accepttells the server the format you'd like in the response:curl -s -H "Accept: application/json" https://jsonplaceholder.typicode.com/posts/1A well-behaved API uses
Acceptto decide whether to return JSON, XML, etc. (content
negotiation). For JSON APIs it's often the default, but sending it explicitly is good hygiene.Authorization — proving who you are
Most real APIs require a token. You send it in the
Authorizationheader, typically as a Bearer token:curl -s -H "Authorization: Bearer <your-token>" https://api.example.com/me- The server reads the token, verifies it, and either serves the request or returns
401 Unauthorized(no/invalid token) or403 Forbidden(valid token, not allowed). - JSONPlaceholder needs no auth, so this is the pattern to recognize — every real API you
integrate (GitHub, Stripe, OpenAI) uses this exact header shape.
Security note: a token is a credential. Never paste real tokens into shared terminals, commit
them to git, or put them in a URL query string — they belong in theAuthorizationheader (or
an environment variable), never in the path.Deliverable for this step: the
Accept-header request output, and in your own words what401vs403mean. - The server reads the token, verifies it, and either serves the request or returns
-
Submit: your request log
Collect your requests and what came back into one file and submit it.
Build your submission
Create
requests.mddocumenting each call with the command and the key result (status + a
snippet of the response):# REST API Lab — <your name> ## 1. GET single + headers $ curl -i .../posts/1 Status: 200 OK · Content-Type: application/json ## 2. GET collection with query params $ curl -s ".../posts?userId=1" <first item returned> ## 3. POST (create) $ curl -i -X POST .../posts -H "Content-Type: application/json" -d '{...}' Status: 201 Created · returned id: 101 ## 4. PUT (update) and DELETE PUT status: 200 · DELETE status: 200 ## 5. Status codes on purpose GET /posts/1 -> 200 GET /posts/99999 -> 404 GET /nonsense -> 404 ## 6. Headers Accept request result + my explanation of 401 vs 403. ## Reflection (2-3 sentences) In my own words: the difference between 4xx and 5xx, and why Content-Type matters on a POST.Submit
git init git add requests.md git commit -m "REST API lab — request log" # push to a public repo or gist, then submit that URLSubmission criteria (self-check)
- You captured a full response (status line + headers) with
-ior-v - You filtered a collection using query parameters (
?key=value) - You created a resource with
POSTand read the201 Createdstatus - You updated with
PUTand deleted withDELETE, reading each status code - You provoked a
404and can explain the2xx/4xx/5xxfamilies - You explained
Content-TypevsAcceptand where a token belongs - Every entry shows the real command and the real status/response
What's next
You can now explore and debug any HTTP API from the terminal. In the Backend Developer Path
you'll flip sides: instead of calling an API, you'll build one — designing the same
endpoints, status codes and JSON bodies you just consumed here. - You captured a full response (status line + headers) with