Skip to main content

How Web APIs Work: HTTP and REST for the SMS – Web API | Lesson 65

Published / updated

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, or DELETE.
  • 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.

PartRequest (client to API)Response (API to client)
First lineMethod and address: GET /api/students/3Status: HTTP/1.1 200 OK
HeadersExtra information, such as Content-Type: application/jsonExtra information, such as Location: /api/students/4
BodyData being sent, for POST and PUTData 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:

PartExampleMeaning
SchemehttpThe protocol; real systems use https, which encrypts everything
HostlocalhostThe computer; localhost means this one
Port5065Which program on that computer
Path/api/students/3Which resource
Query string?include=guardiansOptions, after the ?, joined by &

Example: HTTP methods​

The method says what to do with the resource:

MethodDoesSafeIdempotentSMS example
GETReadYesYesGet Kabir's details
POSTCreateNoNoAdmit a new student
PUTReplace the whole resourceNoYesReplace a student's details
PATCHChange some fieldsNoNot alwaysChange only the address
DELETERemoveNoYesRemove 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:

CodeNameMeans
200OKIt worked; the answer is in the body
201CreatedA new resource was created; Location says where
204No ContentIt worked; there is nothing to send back
400Bad RequestThe request is wrong, such as a missing field
401 / 403Unauthorized / ForbiddenNot signed in / not allowed (Module 12)
404Not FoundNo such resource
409ConflictIt clashes with the current state, such as a duplicate
500Internal Server ErrorA 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.

RuleGoodNot good
Nouns in paths, verbs in methodsPOST /api/studentsPOST /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 result404 Not Found200 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.

src/http-basics/school-office.cs
// 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);
Terminal 1
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.

Terminal 2
curl.exe -i http://localhost:5065/api/students/3
Output
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​

Terminal 2
curl.exe -i http://localhost:5065/api/students/99
Output
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:

src/http-basics/new-student.json
{
"admissionNumber": "ADM-2026-10001",
"fullName": "Ishaan Kumar"
}
Terminal 2
curl.exe -i --json "@src/http-basics/new-student.json" http://localhost:5065/api/students
Output
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:

Terminal 2
curl.exe -i --json "@src/http-basics/invalid-student.json" http://localhost:5065/api/students
Output
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​

Terminal 2
curl.exe -i -X DELETE http://localhost:5065/api/students/4
curl.exe -i -X DELETE http://localhost:5065/api/students/4
Output
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​

  1. Verbs in the path, such as /api/getStudents. The method already says "get". Fix: nouns in paths, verbs in methods.
  2. 200 OK for everything, with errors in the body. Clients and tools cannot tell success from failure. Fix: the right status code.
  3. A GET that changes data. Browsers, caches, and crawlers repeat GETs freely. Fix: change data only with POST, PUT, PATCH, or DELETE.
  4. Typing curl in Windows PowerShell. It runs a different command with different options. Fix: curl.exe.
  5. 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​

From 16 years of .NET 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 GET free of side effects.
  • Return the status code that matches the result; 201 with a Location header 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.
  • GET reads, POST creates, PUT replaces, PATCH changes, and DELETE removes.
  • 2xx means success, 4xx a client mistake, and 5xx a server problem.
  • GET, PUT, and DELETE are idempotent; POST is 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):

Output
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-assignment/065-library-http.cs
// 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);
library-assignment/065-issue-request.json
{
"isbn": "9780143039655",
"studentId": 6
}
Terminal 2
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​

  1. Which method should admit a new student?

    1. GET
    2. POST
    3. DELETE
    4. HEAD
    Show answer

    Answer: B. POST POST creates a new resource; the API answers 201 Created.

  2. What does 204 No Content mean?

    1. The request failed
    2. It worked, and there is nothing to send back
    3. The resource does not exist
    4. The body is being sent later
    Show answer

    Answer: B. It worked, and there is nothing to send back The first DELETE returned 204.

  3. Which header tells the client where a newly created resource lives?

    1. Content-Type
    2. Location
    3. Server
    4. Date
    Show answer

    Answer: B. Location Location: /api/students/4 came with 201 Created.

  4. Which path follows REST rules?

    1. /api/getStudentById?id=3
    2. /api/students/3
    3. /api/Student/3/Get
    4. /api/student-get/3
    Show answer

    Answer: B. /api/students/3 A plural noun and an id; the GET method says what to do.

  5. Sending the same DELETE twice: what is true?

    1. It deletes two students
    2. Student 4 is gone afterwards either way: DELETE is idempotent
    3. The second call returns 500
    4. DELETE is not allowed twice
    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.

See the example · Permalink

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".

See the example · Permalink

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.

See the example · Permalink

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.

See the example · Permalink

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".

See the example · Permalink

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.

See the example · Permalink

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.

See the example · Permalink

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.

See the example · Permalink

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.

See the example · Permalink

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.