HTTP Status Codes Explained: 200, 301, 404, 500 and When You See Them
HTTP status codes are three-digit numbers the server sends back with every response to tell you whether your request succeeded, failed, or needs you to do something else. A 200 means everything worked. A 404 means the resource was not found. A 500 means the server itself broke. You do not need to memorize all 60+ codes, just the dozen or so you will see regularly.
How to read status codes: the first digit tells you everything
Every status code starts with a digit from 1 to 5. That first digit tells you the category:
- 1xx (Informational): "I got your request, still working on it." You will rarely see these.
- 2xx (Success): "It worked."
- 3xx (Redirect): "Go look somewhere else."
- 4xx (Client error): "You made a mistake."
- 5xx (Server error): "I made a mistake."
When you see a status code you do not recognize, look at the first digit. A 418 is a client error (4xx). A 502 is a server error (5xx). That alone tells you where to start debugging.
Success codes (2xx): the ones you want to see
200 OK: The request succeeded. This is the most common response. When you fetch a list of users, load a webpage, or check a payment status, you get 200 if everything is fine.
// 200 OK example
const res = await fetch('/api/users');
console.log(res.status); // 200
const users = await res.json();
// [{ id: 1, name: "Wanjiku" }, ...]201 Created: The server created a new resource. You see this after a successful POST request, like creating a new user account or placing an order.
// 201 Created example
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ item: 'Chapati', quantity: 3 }),
});
console.log(res.status); // 201204 No Content: The request worked, but there is nothing to send back. Common after a DELETE request. The item is gone, and there is no body to return.
Redirect codes (3xx): the resource moved
301 Moved Permanently: The resource has a new URL forever. Search engines will update their index to the new URL. Use this when you rename a page or restructure your site.
// Next.js redirect example
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/old-blog-post',
destination: '/learn/new-blog-post',
permanent: true, // 301
},
];
},
};302 Found (Temporary Redirect): The resource is temporarily at a different URL. The browser should keep using the original URL for future requests.
304 Not Modified: The resource has not changed since the browser last cached it. The server sends this with no body, telling the browser to use its cached version. This saves bandwidth and speeds up loading.
Client error codes (4xx): you sent something wrong
400 Bad Request: Your request is malformed. Maybe you sent invalid JSON, missed a required field, or passed a string where the API expected a number.
// This will likely get a 400
await fetch('/api/users', {
method: 'POST',
body: 'this is not valid JSON',
headers: { 'Content-Type': 'application/json' },
});401 Unauthorized: You are not logged in, or your token expired. The server does not know who you are. Fix: include a valid authentication token.
403 Forbidden: The server knows who you are, but you do not have permission. An admissions officer trying to access the admin panel gets 403, not 401. You are authenticated but not authorized.
404 Not Found: The URL does not match any resource. Either the resource does not exist, or you have a typo in the URL. This is probably the most famous status code on the internet.
422 Unprocessable Entity: The JSON is valid, but the data does not make sense. For example, trying to transfer a negative amount or registering with an email that is already taken.
429 Too Many Requests: You are hitting the API too fast. Most APIs have rate limits. Safaricom's Daraja API, for instance, limits how many STK Push requests you can send per second. Back off and retry with a delay.
Server error codes (5xx): the server broke
500 Internal Server Error: Something went wrong on the server. An unhandled exception, a database query that failed, a null reference. This is the server's way of saying "I crashed and I do not have a better explanation." Check your server logs.
502 Bad Gateway: Your server tried to forward the request to another server (like a database or an upstream API), and that other server returned an invalid response. Common when deploying behind Nginx or a load balancer.
503 Service Unavailable: The server is temporarily down, usually due to maintenance or being overwhelmed with traffic. This is often accompanied by a Retry-After header telling you when to try again.
504 Gateway Timeout: The upstream server took too long to respond. If your API calls an external service (like M-Pesa) and that service is slow, the proxy will return 504 after its timeout expires.
A key point: 5xx errors are never the client's fault. If users are seeing 500 errors, something is broken in your code or infrastructure, and you need to fix it.
Handling status codes in your code
Always check the status code before trying to use the response data. Here is a pattern that works for most fetch calls:
async function fetchUser(userId: string) {
const res = await fetch(`/api/users/${userId}`);
if (res.ok) {
// ok is true for any 2xx status
return await res.json();
}
if (res.status === 404) {
console.log('User not found');
return null;
}
if (res.status === 401) {
// Redirect to login
window.location.href = '/login';
return null;
}
if (res.status === 429) {
// Rate limited, wait and retry
const retryAfter = res.headers.get('Retry-After');
console.log(`Rate limited. Retry after ${retryAfter} seconds.`);
return null;
}
// For everything else, throw
throw new Error(`API error: ${res.status} ${res.statusText}`);
}On the backend, always return the right status code. Do not return 200 with an error message in the body. That makes debugging much harder.
// Express/Next.js API route
export async function POST(req: Request) {
const body = await req.json();
if (!body.email) {
return Response.json(
{ error: 'Email is required' },
{ status: 400 }
);
}
// Create user...
return Response.json(
{ id: 'user_123', email: body.email },
{ status: 201 }
);
}Frequently Asked Questions
- What is the difference between 401 and 403?
- 401 means the server does not know who you are (missing or invalid credentials). 403 means the server knows who you are but you are not allowed to access that resource. Think of 401 as "show me your ID" and 403 as "your ID is fine, but you are not on the guest list."
- Why do I see 200 OK but my app still shows an error?
- Some APIs return 200 for every response and put the actual error in the response body. This is bad practice but common in older APIs. Always read the response body and check for error fields, not just the status code.
- Should I memorize all HTTP status codes?
- No. Learn the ten or so you will see daily (200, 201, 204, 301, 400, 401, 403, 404, 500, 502) and look up the rest when you encounter them. The first digit always tells you the category, which is usually enough to start debugging.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon. Ship 8 production apps with M-Pesa, USSD, and WhatsApp integrations, and get career support until placement.
See Programs