Warm-up: a wrong password on a real account
The answer is 401 with the message "invalid email or password".
401 says the server could not authenticate you, which is the honest description of a wrong password. It is a 4xx because the request was well-formed and simply not accepted, and lesson 3-3's table puts authentication failures exactly there.
The deliberately generic message avoids confirming whether the email exists, which is the enumeration leak from lesson 7-4. Anything more specific, like "wrong password", tells an attacker that the email is a real account and turns a login form into a user-list oracle.
The same string has to come back for both failure modes, which is the part people get wrong. A different message for an unknown email leaks the same information, even though each individual response looks reasonable on its own.
This lesson opens with it because the capstone spec below has to make the same call in real code, alongside the rate limiter that keeps the guessing cheap.
The spec
Time to design a complete service: a personal notes API. Requirements: users sign up, log in, and manage only their own notes.
Applying unit 4's grid, the whole surface is six endpoints:
| Endpoint | Purpose | Auth? | Success |
|---|---|---|---|
POST /signup | create account (hash the password, lesson 7-1) | no | 201 |
POST /login | verify password, start a session (lesson 7-2) | no | 200 |
GET /notes | list my notes, paginated (lesson 4-2) | yes | 200 |
POST /notes | create a note (validated, lesson 5-3) | yes | 201 |
PUT /notes/:id | update my note | yes | 200 |
DELETE /notes/:id | delete my note | yes | 204 |
Failures reuse the lesson 4-3 shape everywhere: 401 unauthorized when not logged in, 403 for someone else's note, 404 for a missing id, 400 invalid_input from validation, 429 from the limiter on /login.
Which stage rejects a note that belongs to someone else
The handler, with 403, because ownership is business logic that needs the note loaded first.
Auth only proves who is asking, and this user is logged in, so a 401 would be wrong. The token or session verified correctly, and nothing about that step knows which notes exist.
Validation only inspects the input shape, so a well-formed body for someone else's note passes it without complaint. Validation answers "is this request sensible", not "is this request permitted".
Ownership requires fetching note 17 and comparing its author to the session user, and that is the handler's job because it is the first stage with the note in hand. The answer is 403, which is the same distinction as your lesson 3-3 problem.
| Question | Stage | Failure |
|---|---|---|
| who are you | auth middleware | 401 |
| is the body sensible | validation | 400 |
| does the note exist | handler | 404 |
| is it yours | handler | 403 |
The 401 versus 403 line is the one worth memorizing. 401 means the server does not know who you are, and 403 means it knows exactly who you are and the answer is still no.
Which status a successful POST /notes carries
201 Created.
A POST that creates a resource answers 201, typically with the created object in the body so the client does not need a second request to learn its new id. Returning the whole object also lets the client see any server-assigned fields, like a timestamp.
Plain 200 is for reads and updates, and 204 is for successful responses with no body, like DELETE. Each of the three says something different about what happened, which is why picking the right one is free information for the caller.
The REST grid from lesson 4-1 pairs each verb with its success code, and the value is consistency. A client that knows POST means 201 across your whole API can write one code path for creation instead of one per endpoint.
Something new now exists on the server, and plain 200 undersells that. A well-behaved 201 also carries a Location header pointing at the new resource, which is the finishing touch on a create endpoint.