HTTP & REST · 35 min

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

Prerequisites

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

  1. 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/1
    

    You get a JSON object — a single "post" with userId, id, title, body. But curl hid the
    status and headers by default. Let's reveal them.

    See the headers with -i

    curl -i https://jsonplaceholder.typicode.com/posts/1
    

    -i includes 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 OK is the status line — protocol version, status code, reason phrase.
    • Content-Type: application/json tells 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, -v shows the exact request and response on the wire.

    Deliverable for this step: the status line and Content-Type header from the -i request.

  2. 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 300 shows 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=value on the URL:

    curl -s "https://jsonplaceholder.typicode.com/posts?userId=1"
    

    This returns only posts where userId is 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=1 filters comments to post 1; _limit=2 caps 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" | jq
    

    Deliverable for this step: the filtered result of the ?userId=1 request (or its first item).

  3. Send data: POST and PUT with a JSON body

    Reading is GET. To create and update, you send a body with POST and PUT.

    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 POST sets 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 is 201 Created (not 200), and the body echoes your
    object with a new "id": 101. 201 is the correct success code for "a new resource was created" —
    distinct from 200 OK for 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/1 replaces the resource at that id with the body you send.
    • The status is 200 OK and the body reflects your update.
    • Semantics to remember: POST to a collection creates; PUT to 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 Created status line and the returned id from the POST.

  4. 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/1
    

    DELETE /posts/1 removes the resource. The response is 200 OK (some APIs use 204 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/null throws the body away.
    • -w "%{http_code}\n" writes just the numeric status. You'll see 200.

    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/nonsense
    

    The status-code families, which you should be able to recite:

    Range Meaning Example
    2xx Success 200 OK, 201 Created, 204 No Content
    3xx Redirect 301 Moved Permanently
    4xx Your request was wrong 400 Bad Request, 401 Unauthorized, 404 Not Found
    5xx Server failed 500 Internal Server Error, 503 Service Unavailable

    When a call fails, the code tells you whose fault it is — 4xx means 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.

  5. 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-Type describes 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

    Accept tells the server the format you'd like in the response:

    curl -s -H "Accept: application/json" https://jsonplaceholder.typicode.com/posts/1
    

    A well-behaved API uses Accept to 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 Authorization header, 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) or 403 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 the Authorization header (or
    an environment variable), never in the path.

    Deliverable for this step: the Accept-header request output, and in your own words what 401 vs 403 mean.

  6. Submit: your request log

    Collect your requests and what came back into one file and submit it.

    Build your submission

    Create requests.md documenting 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 URL
    

    Submission criteria (self-check)

    • You captured a full response (status line + headers) with -i or -v
    • You filtered a collection using query parameters (?key=value)
    • You created a resource with POST and read the 201 Created status
    • You updated with PUT and deleted with DELETE, reading each status code
    • You provoked a 404 and can explain the 2xx/4xx/5xx families
    • You explained Content-Type vs Accept and 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.