How Web APIs Work: HTTP and REST for the SMS – Web API | Lesson 65
Introduction
In Lesson 2, you saw the big picture: a client asks, an API answers, and the database stores. Module 9 built the database side of SchoolApp. Module 10 builds the API, the part every mobile app and website will talk to.
Before writing the real API, you need the language it speaks: HTTP. This lesson sends real HTTP requests to a tiny practice API and reads every line of the answers.
By the end of this lesson, you will be able to:
- Name the parts of an HTTP request and an HTTP response.
- Choose the right method:
GET,POST,PUT,PATCH, orDELETE. - Read status codes such as 200, 201, 204, 400, 404, and 409.
- Send requests with
curl.exe, including a JSON body. - Explain the main REST rules and design SMS addresses that follow them.
Why this matters
Every screen of a school app, from the parent's phone to the principal's dashboard, talks to the API in HTTP. When something goes wrong, the status code and headers are the first clues.
HTTP and REST also come up in nearly every web developer interview, from "what is the difference between PUT and PATCH?" to designing the addresses of a whole system.
The concept
Example: a request and a response
Think of a letter to the school office. The envelope says what you want and where it goes, some notes on the envelope say how to handle it, and the letter inside carries the details. The office sends back a reply with a stamp on top: done, not found, or rejected.
| Part | Request (client to API) | Response (API to client) |
|---|---|---|
| First line | Method and address: GET /api/students/3 | Status: HTTP/1.1 200 OK |
| Headers | Extra information, such as Content-Type: application/json | Extra information, such as Location: /api/students/4 |
| Body | Data being sent, for POST and PUT | Data being returned, usually JSON |
HTTP is stateless: each request carries everything the API needs. The API does not remember your previous request, which is what lets one API serve thousands of users.
Example: the parts of a URL
http://localhost:5065/api/students/3?include=guardians has five parts:
| Part | Example | Meaning |
|---|---|---|
| Scheme | http | The protocol; real systems use https, which encrypts everything |
| Host | localhost | The computer; localhost means this one |
| Port | 5065 | Which program on that computer |
| Path | /api/students/3 | Which resource |
| Query string | ?include=guardians | Options, after the ?, joined by & |
Example: HTTP methods
The method says what to do with the resource:
| Method | Does | Safe | Idempotent | SMS example |
|---|---|---|---|---|
GET | Read | Yes | Yes | Get Kabir's details |
POST | Create | No | No | Admit a new student |
PUT | Replace the whole resource | No | Yes | Replace a student's details |
PATCH | Change some fields | No | Not always | Change only the address |
DELETE | Remove | No | Yes | Remove a student |
Safe means it changes nothing. Idempotent means doing it twice has the same effect as doing it
once. Sending the same POST twice admits two students, which is why payment screens must guard
against double clicks.
Example: status codes
The status code is a three-digit number in the response's first line. The first digit tells you the family:
| Code | Name | Means |
|---|---|---|
| 200 | OK | It worked; the answer is in the body |
| 201 | Created | A new resource was created; Location says where |
| 204 | No Content | It worked; there is nothing to send back |
| 400 | Bad Request | The request is wrong, such as a missing field |
| 401 / 403 | Unauthorized / Forbidden | Not signed in / not allowed (Module 12) |
| 404 | Not Found | No such resource |
| 409 | Conflict | It clashes with the current state, such as a duplicate |
| 500 | Internal Server Error | A bug or failure on the server |
2xx means success, 4xx means the client made a mistake, and 5xx means the server did.
Example: REST
REST is a set of design rules for APIs built on HTTP. The core idea: the API exposes resources, things such as students and invoices, each with its own address, and the HTTP method says what to do with them.
| Rule | Good | Not good |
|---|---|---|
| Nouns in paths, verbs in methods | POST /api/students | POST /api/createStudent |
| Plural collection names | /api/students/3 | /api/student/3 |
| Children under their parent | /api/students/3/fee-invoices | /api/feeInvoicesForStudent?id=3 |
| Lowercase, words joined with hyphens | /api/academic-years | /api/AcademicYears |
| Status codes tell the result | 404 Not Found | 200 OK with {"error": "not found"} |
The SMS reference already follows these rules, for example /api/students/{id}/fee-invoices.
Apply it to the SMS
Step 1: start the practice API
This small program is not part of SchoolApp. It keeps three students in memory, so you can practise HTTP without touching SchoolDb. Lesson 68 explains how its code works; today, look only at the HTTP.
// Lesson 65: a tiny web API, only for seeing HTTP requests and responses. It is not part of
// SchoolApp, and it keeps three students in memory. How the code works comes in Lesson 68.
// Run with: dotnet run src/http-basics/school-office.cs
// Then send requests from a second terminal with curl.exe (see Lesson 65).
#:sdk Microsoft.NET.Sdk.Web
#:property PublishAot=false
WebApplication app = WebApplication.CreateBuilder(args).Build();
List<Student> students =
[
new Student(1, "ADM-2017-0001", "Aarav Sharma"),
new Student(2, "ADM-2017-0002", "Diya Patel"),
new Student(3, "ADM-2017-0003", "Kabir Singh"),
];
// GET: read. Safe (changes nothing) and idempotent (the same answer every time).
app.MapGet("/api/students", () => students);
app.MapGet("/api/students/{id:int}", (int id) =>
students.FirstOrDefault(s => s.Id == id) is Student student
? Results.Ok(student) // 200 OK, with the student as JSON
: Results.NotFound()); // 404 Not Found
// POST: create. The new student's address comes back in the Location header.
app.MapPost("/api/students", (NewStudent request) =>
{
if (string.IsNullOrWhiteSpace(request.FullName))
{
return Results.BadRequest(new { error = "FullName is required." }); // 400 Bad Request
}
Student student = new Student(students.Max(s => s.Id) + 1, request.AdmissionNumber, request.FullName);
students.Add(student);
return Results.Created($"/api/students/{student.Id}", student); // 201 Created
});
// DELETE: remove. Idempotent: after the first call, the student is gone either way.
app.MapDelete("/api/students/{id:int}", (int id) =>
students.RemoveAll(s => s.Id == id) > 0
? Results.NoContent() // 204 No Content: done, nothing to send back
: Results.NotFound());
app.Run("http://localhost:5065");
record Student(int Id, string AdmissionNumber, string FullName);
record NewStudent(string AdmissionNumber, string FullName);
dotnet run src/http-basics/school-office.cs
It is a .NET 10 single-file app, like the homework apps since Lesson 30. The #:sdk line makes it a
web app, and it listens on http://localhost:5065 until you press Ctrl+C.
Step 2: send requests with curl.exe
curl is a command-line tool for sending HTTP requests, built into Windows 10 and 11. In Windows
PowerShell, type curl.exe, not curl: plain curl there is a different command. The -i option
shows the status line and headers, not just the body.
curl.exe -i http://localhost:5065/api/students/3
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 07 Oct 2026 14:23:58 GMT
Server: Kestrel
Transfer-Encoding: chunked
{"id":3,"admissionNumber":"ADM-2017-0003","fullName":"Kabir Singh"}
Read it top to bottom: status 200 OK, then headers, then an empty line, then the body. The
Content-Type header says the body is JSON. Kestrel is the web server inside ASP.NET Core.
Example: a resource that does not exist
curl.exe -i http://localhost:5065/api/students/99
HTTP/1.1 404 Not Found
Content-Length: 0
Date: Wed, 07 Oct 2026 14:23:58 GMT
Server: Kestrel
No student 99, so 404 Not Found, with an empty body (Content-Length: 0). The client knows what
happened from the status code alone, without reading any text.
Example: creating with POST
A POST sends a body. Writing JSON on the PowerShell command line is awkward, so the body sits in a
file, and --json "@file" sends it with the right Content-Type header:
{
"admissionNumber": "ADM-2026-10001",
"fullName": "Ishaan Kumar"
}
curl.exe -i --json "@src/http-basics/new-student.json" http://localhost:5065/api/students
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Wed, 07 Oct 2026 14:23:58 GMT
Server: Kestrel
Location: /api/students/4
Transfer-Encoding: chunked
{"id":4,"admissionNumber":"ADM-2026-10001","fullName":"Ishaan Kumar"}
201 Created says something new exists, and the Location header gives its address. The body
returns the new student, including the id the API chose.
Example: a bad request
invalid-student.json has an empty fullName:
curl.exe -i --json "@src/http-basics/invalid-student.json" http://localhost:5065/api/students
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Wed, 07 Oct 2026 14:23:59 GMT
Server: Kestrel
Transfer-Encoding: chunked
{"error":"FullName is required."}
A 4xx code says the client must fix the request; sending it again unchanged will fail again. Lesson
73 replaces this hand-written check with automatic validation.
Example: DELETE, twice
curl.exe -i -X DELETE http://localhost:5065/api/students/4
curl.exe -i -X DELETE http://localhost:5065/api/students/4
HTTP/1.1 204 No Content
Date: Wed, 07 Oct 2026 14:23:59 GMT
Server: Kestrel
HTTP/1.1 404 Not Found
Content-Length: 0
Date: Wed, 07 Oct 2026 14:23:59 GMT
Server: Kestrel
-X DELETE sets the method. The first call removed student 4 (204 No Content), and the second found
nothing to remove (404).
Either way, student 4 is gone afterwards, which is what idempotent means.
Common mistakes
- Verbs in the path, such as
/api/getStudents. The method already says "get". Fix: nouns in paths, verbs in methods. 200 OKfor everything, with errors in the body. Clients and tools cannot tell success from failure. Fix: the right status code.- A
GETthat changes data. Browsers, caches, and crawlers repeatGETs freely. Fix: change data only withPOST,PUT,PATCH, orDELETE. - Typing
curlin Windows PowerShell. It runs a different command with different options. Fix:curl.exe. - Sending JSON without
Content-Type: application/json. The API may reject or misread it. Fix:--json, which sets the header for you.
In real projects
A school's mobile app once showed "Fee paid!" to parents whose payments had failed. The API returned
200 OK for every request and put errors inside the body as text.
The app's developers checked only the status code, which was always 200. Fixing the API to return
409 for a rejected payment, and 400 for bad input, fixed the app without changing a line of it.
Since then, I review status codes first in every API design. They are the contract every client reads, even clients that never read the body.
Best practices
- Design paths as plural nouns, nested under their parent where it makes sense.
- Use each HTTP method for its meaning, and keep
GETfree of side effects. - Return the status code that matches the result;
201with aLocationheader for creations. - Use JSON with
Content-Type: application/json. - Use HTTPS for every real system, never plain HTTP.
Recap
- A request has a method, a URL, headers, and an optional body; a response has a status, headers, and a body.
GETreads,POSTcreates,PUTreplaces,PATCHchanges, andDELETEremoves.2xxmeans success,4xxa client mistake, and5xxa server problem.GET,PUT, andDELETEare idempotent;POSTis not.- REST exposes resources at noun addresses, and lets the method and status code do the talking.
Homework
Library module task: Run the practice library API below. With curl.exe -i, get The Jungle Book by
its ISBN, ask for a book that does not exist, and then issue Malgudi Days twice with the request file.
Before each request, write down the status code you expect.
A correct result looks like (status lines and bodies only):
HTTP/1.1 200 OK
{"isbn":"9780141325293","title":"The Jungle Book","availableCopies":0}
HTTP/1.1 404 Not Found
HTTP/1.1 201 Created
Location: /api/book-issues/1
{"id":1,"isbn":"9780143039655","studentId":6}
HTTP/1.1 409 Conflict
{"error":"No copy of Malgudi Days is available."}
Show the solution
// Library homework, Lesson 65: a tiny library API in memory, for practising HTTP methods and
// status codes with curl.exe. Not connected to SchoolDb yet.
// Run with: dotnet run library-assignment/065-library-http.cs
#:sdk Microsoft.NET.Sdk.Web
#:property PublishAot=false
WebApplication app = WebApplication.CreateBuilder(args).Build();
List<Book> books =
[
new Book("9788173711466", "Wings of Fire", AvailableCopies: 2),
new Book("9780141325293", "The Jungle Book", AvailableCopies: 0),
new Book("9780143039655", "Malgudi Days", AvailableCopies: 1),
];
int nextIssueId = 1;
app.MapGet("/api/books", () => books);
app.MapGet("/api/books/{isbn}", (string isbn) =>
books.FirstOrDefault(b => b.Isbn == isbn) is Book book ? Results.Ok(book) : Results.NotFound());
// Issuing a book creates a book issue: 201 Created, or 409 Conflict when no copy is free.
app.MapPost("/api/book-issues", (IssueRequest request) =>
{
int index = books.FindIndex(b => b.Isbn == request.Isbn);
if (index < 0)
{
return Results.NotFound();
}
if (books[index].AvailableCopies == 0)
{
return Results.Conflict(new { error = $"No copy of {books[index].Title} is available." });
}
books[index] = books[index] with { AvailableCopies = books[index].AvailableCopies - 1 };
BookIssue issue = new BookIssue(nextIssueId++, request.Isbn, request.StudentId);
return Results.Created($"/api/book-issues/{issue.Id}", issue);
});
app.Run("http://localhost:5066");
record Book(string Isbn, string Title, int AvailableCopies);
record IssueRequest(string Isbn, int StudentId);
record BookIssue(int Id, string Isbn, int StudentId);
{
"isbn": "9780143039655",
"studentId": 6
}
curl.exe -i http://localhost:5066/api/books/9780141325293
curl.exe -i http://localhost:5066/api/books/123
curl.exe -i --json "@library-assignment/065-issue-request.json" http://localhost:5066/api/book-issues
curl.exe -i --json "@library-assignment/065-issue-request.json" http://localhost:5066/api/book-issues
Malgudi Days has one copy, so the first issue gets 201 and the second gets 409 Conflict: the request
was fine, but it clashes with the current state of the library.
Quiz
Which method should admit a new student?
Show answer
Answer: B. POST POST creates a new resource; the API answers 201 Created.
What does 204 No Content mean?
Show answer
Answer: B. It worked, and there is nothing to send back The first DELETE returned 204.
Which header tells the client where a newly created resource lives?
Show answer
Answer: B. Location Location: /api/students/4 came with 201 Created.
Which path follows REST rules?
Show answer
Answer: B. /api/students/3 A plural noun and an id; the GET method says what to do.
Sending the same DELETE twice: what is true?
Show answer
Answer: B. Student 4 is gone afterwards either way: DELETE is idempotent 204 the first time, 404 the second, with the same final state.
Interview questions
Questions are grouped by experience level. Click a question to see a model answer. All questions: Web API interview questions.
Fresher (0-2 yrs)
F What are the parts of an HTTP request and an HTTP response? concept
A request has a method and URL, headers, and an optional body. A response has a status line with a
status code, headers, and an optional body. For example, POST /api/students with a JSON body got
201 Created, a Location header, and the new student as JSON.
F What is the difference between 400 and 409, and between 401 and 403? concept
400 Bad Request means the request itself is wrong, such as an empty name; sending it again unchanged fails again. 409 Conflict means the request is valid but clashes with the current state, such as issuing a book with no free copy. 401 means "not signed in"; 403 means "signed in, but not allowed".
F What is a query string, and when do you use it instead of a path segment? concept
The part of a URL after ?, with name=value pairs joined by &. Use the path to identify a
resource, such as /api/students/3, and the query string for options on it, such as paging,
sorting, or filtering: /api/students?page=2&pageSize=20.
Mid (3-5 yrs)
M What do safe and idempotent mean for HTTP methods? concept
Safe methods change nothing on the server, such as GET. Idempotent methods have the same effect
whether sent once or many times: GET, PUT, and DELETE are; POST is not. Deleting student 4
twice returned 204 then 404, but student 4 was gone either way.
M What is the difference between PUT and PATCH? concept
PUT replaces the whole resource with the body sent, so missing fields are treated as cleared, and
sending it twice gives the same result. PATCH changes only the fields sent; whether it is
idempotent depends on the change, such as "add 1" versus "set to 5".
M What makes an API RESTful? Give examples of good and bad paths. design
Resources with their own addresses, nouns in paths and verbs in methods, plural collections,
children under parents, statelessness, and status codes that report the result. Good:
GET /api/students/3/fee-invoices. Bad: POST /api/getFeeInvoicesForStudent?id=3.
Senior (6-9 yrs)
S A mobile app shows success for failed fee payments. The API returns 200 with an error message in the body. What do you change? scenario
Return status codes that match the result: 400 for invalid input, 404 for a missing invoice, 409 for a conflict such as an already-paid invoice, and 2xx only for real success. Put details in a standard error body. Clients that check only the status code then behave correctly without changes.
How to answer: Explain why status codes are the contract every client and tool reads, map each failure to a code, and mention a standard error format (ProblemDetails, Lesson 74) and versioning if clients depend on the old behaviour.
S A parent double-clicks 'Pay'. How do you stop two payments when POST is not idempotent? design
Make the operation idempotent: the client sends a unique idempotency key with the request, and the server stores it with the payment and returns the first result for any repeat with the same key. Disable the button after the first click too, but never rely on the client alone.
How to answer: Name the problem (non-idempotent POST), describe idempotency keys with a unique constraint in the database, and add the UI and concurrency safeguards as extra layers.
Architect / Lead (10+ yrs)
A Why does statelessness matter when an API must serve many schools and users? design
When each request carries everything needed, such as identity in a token and the ids in the URL, any server can handle any request. You can add servers behind a load balancer, restart one without losing sessions, and cache responses. Server-side session state ties users to one machine and limits scaling.
How to answer: Connect statelessness to horizontal scaling, resilience, and caching, and say where state goes instead: the client, tokens, the database, or a distributed cache.
Resources
- Code for this lesson: git tag
lesson-65-http-rest-basics(code repository coming soon) - Files:
src/http-basics/school-office.cs,src/http-basics/new-student.json,library-assignment/065-library-http.cs - Video: coming soon
What's next
You can now read HTTP. Next: creating the real SchoolApp.Api project and looking at every file it
starts with.
Lesson 66: Creating SchoolApp.Api
Want live sessions, code reviews, and interview preparation? Join the next batch at Sahasra Technologies.