Web Foundations & Databases · beginner · ~10 min

REST APIs, HTTP methods, and JSON

By the end of this lesson you will be able to: - Read any REST request and say in plain English what it does to which resource. - Match each HTTP method (GET, POST, PUT, PATCH, DELETE) to the action it performs and know which ones are safe or idempotent. - Recognise JSON at a glance and identify its four building blocks: objects, arrays, scalars, and `null`. - Take a REST request apart into its three security-relevant pieces — the method, the JSON body, and the resource ID in the URL. - Read HTTP status codes well enough to tell success from client error from server error. - Drive a real API by hand with `curl`, and explain why the same three pieces are where verb tampering, mass assignment, and IDOR appear.

Overview

Almost every modern app — a phone app, a single-page website, one backend calling another — talks to its server through an API (Application Programming Interface). An API is just an agreed set of requests a server promises to answer. The most common style today is REST over HTTP, and the data it carries is almost always JSON.

This lesson builds directly on How the web works: client, server, request, response. There you saw that a client sends an HTTP request (a method, a URL, headers, and an optional body) and the server sends back a response (a status code, headers, and a body). REST is a set of conventions layered on top of that raw request/response machinery. It answers two questions: what does the URL point at? and what do I want to do to it?

In plain language: REST treats the things your app manages — users, orders, photos, comments — as resources, and gives each one a URL, like /api/users/42. The HTTP method is the verb that says what to do with that resource: read it, create one, change it, or delete it. The data going in or coming back rides along as JSON, a simple text format that both humans and programs can read.

The terminology you will meet: resource (a thing with a URL), method or verb (GET/POST/PUT/PATCH/DELETE), endpoint (a method + URL pair the server handles), payload or body (the JSON you send), and status code (the three-digit result). Once these click, an API stops looking like magic and starts looking like a small, predictable vocabulary.

Why it matters

APIs are where the real work — and the real risk — lives. The pretty web page is just a client; the API behind it is the backend's true attack surface, and it will happily answer a request whether it came from the official app or from a script you wrote in thirty seconds.

Being fluent in REST + JSON pays off three ways:

  • You can build clients. Fetching data, submitting forms, talking to third-party services (payments, maps, weather, AI models) all mean composing REST requests and parsing JSON responses.
  • You can debug. When an app misbehaves, the answer is usually in the request or the response. Reading the method, body, and status code tells you who is at fault — client or server.
  • You can reason about security. Three parts of every REST request map to three of the most common API flaws in the world: the method (verb tampering), the JSON fields (mass assignment), and the ID in the URL (IDOR — Insecure Direct Object Reference). You cannot spot these flaws until you can read the request that carries them. This lesson is the vocabulary; the exploitation and defence are covered in the API Security track.

Core concepts

1. Resources are URLs

Definition. A resource is any thing your API manages, and REST gives each resource (and each collection of them) a URL.

Think of URLs as an address book for your data. A collection is a plural noun, and a single item is that collection plus an ID:

/api/users          -> the collection of all users
/api/users/42       -> one user, the one with id 42
/api/users/42/orders-> the orders belonging to user 42

How it works internally. The server has a router — a table that matches an incoming URL pattern (like /api/users/{id}) to a function. The {id} part is pulled out as a variable and handed to your code, which typically looks it up in a database.

When to use / not to. Model nouns as resources (/api/orders), not actions. A URL like /api/deleteUser?id=42 is a classic beginner smell — the verb belongs in the method, not the path. Use DELETE /api/users/42 instead.

Pitfall. Because the ID sits right there in the URL, it is trivial to change. If the server does not check that you are allowed to see user 42, changing 42 to 43 hands you someone else's data — that is IDOR.

Knowledge check. In GET /api/projects/7/tasks/3, name every resource the URL references and say which method verb is acting on them.

2. Methods are verbs

Definition. The HTTP method states the action to perform on the resource named by the URL.

Method Action Safe? Idempotent? Typical body
GET Read a resource Yes Yes none
POST Create a new resource No No JSON
PUT Replace a resource wholesale No Yes full JSON
PATCH Update part of a resource No No* partial JSON
DELETE Remove a resource No Yes usually none

Safe means "read-only, no server state changes." Idempotent means "doing it twice has the same effect as doing it once" — deleting user 42 twice still leaves user 42 deleted, but POSTing an order twice creates two orders. (PATCH can be idempotent depending on the change, so it is marked with an asterisk.)

How it works internally. The method is the very first token on the HTTP request line: GET /api/users/42 HTTP/1.1. The server routes on the pair (method, path), so GET /api/users/42 and DELETE /api/users/42 can run completely different code.

When to use / not to. Use GET only for reads — never let a GET change data, because browsers, proxies, and crawlers prefetch and cache GETs freely. Use POST to create, PUT/PATCH to update, DELETE to remove.

Pitfall — verb tampering. A UI might only show a "view" button (GET) but the server may still answer DELETE or PUT on the same URL because the developer forgot to restrict it. Hiding a button is not access control.

Knowledge check (predict). You send DELETE /api/users/42 and it returns success. You send the exact same request again. Is the second call safe to make, and what status might it return the second time?

3. JSON is the payload

Definition. JSON (JavaScript Object Notation) is a lightweight text format for structured data. It is the default body format for REST APIs.

JSON has exactly four building blocks plus two container types:

  • Objects: { "key": value, ... } — unordered name/value pairs.
  • Arrays: [ value, value, ... ] — ordered lists.
  • Scalars: strings ("hi"), numbers (42, 3.14), booleans (true/false).
  • Null: the literal null.
{
  "id": 42,                 <- number scalar
  "name": "Ada",           <- string scalar
  "active": true,           <- boolean scalar
  "nickname": null,         <- explicit "no value"
  "roles": ["user","beta"], <- array of strings
  "address": {              <- nested object
    "city": "Lovelace"
  }
}

How it works internally. JSON is just text on the wire. The sender serializes a data structure into that text; the receiver parses it back into objects your code can read. Keys are always strings in double quotes. There are no comments, no trailing commas, and no single quotes — those are the top three syntax errors.

When to use / not to. JSON is ideal for structured request and response bodies. It is not meant for huge binary blobs (images, video) — those are sent as raw bytes or multipart uploads.

Pitfall — mass assignment. If the server takes the whole JSON body and blindly copies every field onto a database record, a client can add a field the form never showed — like "role": "admin" — and quietly promote itself. The fix is to accept only an explicit allow-list of fields.

Knowledge check (find the bug). Why is { 'name': 'Ada', "age": 30, } invalid JSON? Point to two separate problems.

4. Status codes are the result

Definition. The response status code is a three-digit number summarising what happened. The first digit is the category.

Range Meaning Common examples
2xx Success 200 OK, 201 Created, 204 No Content
3xx Redirection 301 Moved, 304 Not Modified
4xx Client error (your request was wrong) 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
5xx Server error (the server broke) 500 Internal Server Error, 503 Service Unavailable

Why it matters. The status code tells you whose fault it is. A 4xx means fix your request; a 5xx means the server crashed on a request it should have handled. The security-relevant pair is 401 ("I don't know who you are") versus 403 ("I know who you are, and you're not allowed").

Knowledge check (explain). In your own words, why is returning 404 sometimes safer than 403 when a user requests a resource that exists but isn't theirs?

Syntax notes

A REST request has four parts. Here is the anatomy, annotated:

POST /api/users/42/orders HTTP/1.1      <- method + resource URL + HTTP version
Host: api.example.com                    <- which server
Authorization: Bearer eyJhbGci...        <- who you are (a token)
Content-Type: application/json           <- the body is JSON
Content-Length: 27                       <- how many bytes of body follow
                                         <- blank line separates headers from body
{ "item": "book", "qty": 2 }             <- the JSON payload

And a response:

HTTP/1.1 201 Created                      <- status code + reason phrase
Content-Type: application/json

{ "id": 1001, "item": "book", "qty": 2 }  <- the created resource, as JSON

Key rules to remember:

  • The method and path are the first line; the server routes on both.
  • Content-Type: application/json announces a JSON body — omit it and many servers ignore your body.
  • A blank line always separates headers from the body.
  • GET and DELETE usually carry no body; POST/PUT/PATCH usually do.

Lesson

Modern apps talk to their backend through APIs. The most common style is REST over HTTP, exchanging JSON data.

The shape of a REST request

In REST, resources are URLs, and the HTTP method is the verb that says what to do with them:

  • GET — read data (no side effects).
  • POST — create something new.
  • PUT / PATCH — update an existing thing.
  • DELETE — remove it.

For example:

  • GET /api/users/42 reads user 42.
  • DELETE /api/users/42 removes user 42.

JSON

Data travels as JSON (JavaScript Object Notation), a human-readable text format and the default for APIs.

JSON can hold:

  • Objects, such as {"id":42,"role":"user"}.
  • Arrays (ordered lists).
  • Scalars: strings, numbers, booleans, and null.

Why this matters for testing

Each part of a REST request maps to a common flaw:

  • The method matters. A resource might hide DELETE in the UI but still accept it when called directly. This is verb tampering.
  • JSON bodies carry the data. Mass assignment lives here — for example, sending "role":"admin" to an endpoint that blindly binds whatever fields it receives.
  • IDs in URLs identify resources. IDOR lives here — changing /users/42 to /users/43 to reach another user's data.

A web tester reads and rewrites these requests constantly using curl, Postman, or Burp. The full API attack surface is covered in the API Security track.

Code examples

Here is a complete, runnable session against a real public sandbox API (jsonplaceholder.typicode.com, a free fake REST API for practice). Every command uses curl, the standard command-line HTTP client. The -s flag silences the progress meter; -i includes the response status line and headers.

# 1) GET — read a single resource (a to-do item). Safe, no body.
curl -s https://jsonplaceholder.typicode.com/todos/1

# 2) GET a collection, then filter to the first two with a query string.
curl -s 'https://jsonplaceholder.typicode.com/todos?_limit=2'

# 3) POST — create a new resource. Send a JSON body and the matching header.
curl -s -i -X POST https://jsonplaceholder.typicode.com/posts \
  -H 'Content-Type: application/json' \
  -d '{ "title": "hello", "body": "first post", "userId": 1 }'

# 4) PATCH — update part of an existing resource.
curl -s -X PATCH https://jsonplaceholder.typicode.com/posts/1 \
  -H 'Content-Type: application/json' \
  -d '{ "title": "updated title" }'

# 5) DELETE — remove a resource. No body needed.
curl -s -i -X DELETE https://jsonplaceholder.typicode.com/posts/1

What it does. Command 1 reads to-do #1. Command 2 reads the collection but asks the server for only two items via the _limit query parameter. Command 3 creates a post; because the server invents an ID, it replies 201 Created with the new resource. Command 4 changes just the title of post 1. Command 5 removes post 1 and replies 200 (this sandbox does not actually persist changes, but it echoes what a real server would do).

Expected output (abridged).

# From command 1:
{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }

# From command 3, the status line and echoed body:
HTTP/2 201
content-type: application/json; charset=utf-8

{ "title": "hello", "body": "first post", "userId": 1, "id": 101 }

Edge cases to notice. If you forget -H 'Content-Type: application/json' on the POST, the server may not parse your body and you get an empty resource back. If your JSON has a trailing comma or single quotes, the request either fails to parse or the shell mangles it — always wrap the -d payload in single quotes so the shell leaves the double-quoted JSON intact.

Line by line

Let's trace the POST request (command 3) from keystroke to response.

  1. curl -X POST .../postscurl opens a TLS connection to the host and writes the request line POST /posts HTTP/2. The method POST tells the server: create a new resource in the posts collection.
  2. -H 'Content-Type: application/json' — adds a header announcing that the bytes in the body are JSON. Without it, the server's body parser may skip the body entirely.
  3. -d '{ "title": "hello", ... }' — supplies the request body. curl also sets Content-Length automatically to the byte count of this string.
  4. On the server, the router matches (POST, /posts) to the "create post" handler. It parses the JSON text back into a structure, reads the fields, assigns a fresh id (101 here), and stores the record.
  5. The response comes back with status 201 Created — the 2xx family means success, and 201 specifically means "a new resource now exists." The body echoes the created object including its new id, so the client learns the ID it will use later.

Here is how the meaningful values change across the round trip:

Stage What exists id value
Before send client's 3-field JSON none
On the wire same JSON as text bytes none
After server parse server-side object assigned = 101
In the response echoed object 101

The DELETE (command 5) is simpler: the router matches (DELETE, /posts/1), the handler removes record 1, and returns 200 (or 204 No Content on many real APIs, meaning "done, nothing to send back"). Notice there is no body going up — the URL alone identifies what to delete.

Common mistakes

Mistake 1 — putting the verb in the URL.

GET /api/deleteUser?id=42     <- WRONG: a GET that deletes

Why it's wrong: GET is supposed to be safe (read-only). Browsers, link-prefetchers, and crawlers fetch GET URLs automatically — one of them could silently delete data. Corrected:

DELETE /api/users/42          <- verb in the method, noun in the URL

Recognise it: any URL containing a verb (create, delete, update) paired with GET.

Mistake 2 — sending JSON without the Content-Type header.

curl -X POST .../posts -d '{"title":"hi"}'   # server may ignore the body

Why it's wrong: the server doesn't know the body is JSON, so its JSON parser never runs and your fields arrive empty. Corrected: add -H 'Content-Type: application/json'. Recognise it: you POST data but the created resource comes back blank.

Mistake 3 — invalid JSON syntax.

{ 'name': 'Ada', "age": 30, }   <- single quotes AND a trailing comma

Why it's wrong: JSON requires double-quoted keys and strings and forbids the trailing comma after the last pair. Corrected:

{ "name": "Ada", "age": 30 }

Recognise it: a 400 Bad Request with a message like "Unexpected token."

Mistake 4 — assuming a 2xx means your data was correct.

A server can return 200 OK while quietly ignoring a field it didn't recognise, or 201 while silently dropping your extra keys. Always read the echoed response body and confirm it contains what you expect, rather than trusting the status code alone.

Mistake 5 — confusing 401 and 403. Treating them as interchangeable hides real bugs. 401 = "you are not authenticated (log in)." 403 = "you are authenticated but not permitted." Retrying a 403 with the same credentials will never succeed; retrying a 401 after logging in might.

Debugging tips

Common errors and how to chase them:

  • 400 Bad Request — almost always malformed JSON or a missing required field. Paste the body into a JSON validator (or run it through python3 -m json.tool) to find the exact syntax error.
  • 401 Unauthorized — you sent no token, an expired token, or a malformed Authorization header. Check the header is spelled Authorization: Bearer <token> exactly.
  • 403 Forbidden — the token is valid but your account lacks permission. Do not keep retrying; the request itself is fine.
  • 404 Not Found — either the URL is misspelled or the resource ID doesn't exist. Confirm the path with a known-good GET on the collection first.
  • 405 Method Not Allowed — right URL, wrong verb. The endpoint exists but doesn't accept, say, DELETE. Check the API docs for allowed methods.
  • 415 Unsupported Media Type — you forgot Content-Type: application/json.
  • Empty or wrong data created — the body was sent but not parsed; verify the Content-Type header and that the shell didn't eat your quotes.

A repeatable debugging routine:

  1. Add -i (or -v for full detail) to curl so you can see the status line and headers, not just the body.
  2. Read the status code first — it tells you whether to fix the request (4xx) or report a server bug (5xx).
  3. Validate your JSON body independently before blaming the server.
  4. Reproduce a known-good GET on the same resource to confirm the URL and auth are correct, then change one thing at a time.

Questions to ask when it doesn't work: Is the method right for the action? Is the URL pointing at a real resource? Did I announce and send valid JSON? Do I have a valid token, and does my account have permission?

Memory safety

This is a concept lesson, so the concern is robustness and safe handling of untrusted input rather than C memory bugs — but the mindset carries straight into the parsing exercises attached to this lesson.

Never trust the client. Every byte of a request — method, URL, headers, and JSON body — is fully attacker-controlled. Defensive habits:

  • Validate the method against an allow-list per endpoint. If an endpoint is read-only, reject anything but GET rather than assuming clients will behave. Not doing so enables verb tampering.
  • Bind only an explicit allow-list of JSON fields. Never copy the whole body onto a database record. Accepting arbitrary keys is exactly the mass assignment flaw — a client adding "role":"admin" or "isVerified":true to a field the form never offered.
  • Authorize the ID, don't just authenticate the caller. Being logged in is not the same as being allowed to see resource 42. Check ownership on every ID lookup, or you have an IDOR. Returning 404 instead of 403 for another user's resource avoids leaking that the ID even exists.
  • Cap body size and validate types. A JSON number where you expected a string, a deeply nested object, or a multi-megabyte payload can crash a naive parser. Set size limits and check types before use.
  • Parsing is where buffer overflows live. When you drop down to the C exercises (http_parse_request_line, parse_request_line), the request line is raw untrusted bytes. Bounds-check every copy into a fixed buffer — a missing length check on a copied path is a classic buffer-overflow CVE. Always pass and honour the destination buffer size.

These three flaws — verb tampering, mass assignment, IDOR — are defensive lab concepts here; the offensive testing is covered later in the API Security track against intentionally vulnerable practice targets only.

Real-world uses

Where REST + JSON shows up. Nearly everywhere a program talks to another program over the internet: a mobile app fetching your feed, a single-page web app calling its backend, a payment integration (Stripe, PayPal), maps and weather services, cloud provider control planes (AWS, GCP), and AI model APIs. When you 'log in with Google' or your phone shows the latest messages, REST requests carrying JSON are doing the work underneath.

Professional best-practice habits:

  • Beginner: Use nouns for URLs and verbs for methods. Always send Content-Type: application/json with a body. Read the status code before the body. Validate your JSON before sending. Keep secrets (tokens) out of URLs — put them in the Authorization header, never in query strings, which get logged.
  • Advanced: Version your API (/v1/...) so you can evolve it without breaking clients. Return consistent, machine-readable error bodies. Use correct status codes (201 for create, 204 for delete, 422 for validation errors). Paginate large collections instead of returning everything. Enforce authorization on every resource ID. Rate-limit and cap request body size. Document each endpoint's allowed methods and required fields.

Readability and naming. Predictable, plural, lowercase resource names (/api/orders, not /API/getOrderList) make an API self-documenting. Consistent field naming in JSON (pick snake_case or camelCase and stick to it) saves every future client a lookup.

Practice tasks

Beginner 1 — Classify the requests. Given these five requests, state for each one (a) the resource, (b) the action in plain English, and (c) whether it is safe and/or idempotent:

GET /api/books
GET /api/books/17
POST /api/books
PUT /api/books/17
DELETE /api/books/17

Concepts: methods as verbs, safe vs idempotent. Hint: build a small table with one row per request.

Beginner 2 — Validate JSON. Here are three candidate bodies. For each, decide whether it is valid JSON, and if not, name every error:

A) { "id": 5, "tags": ["new", "sale"] }
B) { 'id': 5 }
C) { "id": 5, "active": true, }

Concepts: JSON syntax rules. Hint: check quoting and trailing commas.

Intermediate 1 — Compose curl commands. Using the jsonplaceholder.typicode.com sandbox, write the exact curl command to (a) read comment 4, (b) create a new post with a title and body, and (c) update only the completed field of todo 1 to true. Include the correct method flag and, where needed, the Content-Type header. Concepts: method selection, request bodies, headers. Hint: creation uses POST; a partial update uses PATCH.

Intermediate 2 — Map flaws to request parts. For each of the three flaws below, name which part of a REST request it targets (method / JSON body / URL ID) and write one sentence describing a defensive check that prevents it: verb tampering, mass assignment, IDOR. Concepts: the three attack hooks, defensive validation. Hint: one flaw per request part.

Challenge — Design a mini REST API. On paper, design the endpoints for a simple 'library' service that manages books and each book's reviews. Specify: the URL and method for listing books, reading one book, creating a book, updating a book, deleting a book, listing a book's reviews, and adding a review. For the 'create book' endpoint, list exactly which JSON fields you will accept (an allow-list) and which you will reject to avoid mass assignment, and pick the correct success status code for each write operation. Concepts: resource modelling, method selection, status codes, allow-list validation. Hint: reviews are a nested collection under a book; do not accept an id or owner field from the client on create.

Summary

  • REST exposes your data as resources with URLs (/api/users/42) and uses the HTTP method as the verb: GET reads, POST creates, PUT/PATCH update, DELETE removes. GET is safe; GET, PUT, and DELETE are idempotent.
  • Data travels as JSON — objects {}, arrays [], scalars (string/number/boolean), and null. Double-quote every key and string; no trailing commas, no comments.
  • A request is method + URL + headers + body; a response is status code + headers + body. Read the status code first: 2xx success, 4xx your fault, 5xx the server's fault; 401 = not logged in, 403 = not allowed.
  • The most common syntax mistakes: verbs in the URL, missing Content-Type: application/json, and invalid JSON (single quotes, trailing commas).
  • The three security-relevant parts map to three flaws — method → verb tampering, JSON fields → mass assignment, URL ID → IDOR. The defence is always the same shape: validate the method, allow-list the fields, and authorize the ID. That vocabulary is the foundation for the API Security track ahead.

Practice with these exercises