HTTP 500 vs 400 Error Handling: How Wrong Status Codes Wake Up Your On-Call
A 500 for a typo in a form field pages someone for nothing. Here is how to pick the right 4xx or 5xx code, and how to alert only on what needs a human.
Most teams that struggle with noisy alerts look first at their monitoring tool. The real cause is often much earlier: the API returns the wrong status code. When a user sends a bad email address and your service answers 500, your monitoring sees a server failure. Your on-call engineer sees a page. Nothing is broken.
This post covers HTTP 500 vs 400 error handling in plain terms: what the two classes mean, which code fits which situation, and how to set alerts so they fire only when your system is the problem.
What 4xx and 5xx actually mean
The HTTP standard (RFC 9110) splits error responses into two classes.
- 4xx, client error. The request contains bad syntax or cannot be fulfilled by the server as sent. The fix belongs on the caller's side.
- 5xx, server error. The server is aware that it has erred or is incapable of performing the request. The fix belongs on your side.
That split is the whole idea. A 4xx says "your request was the problem." A 5xx says "we were the problem." Every tool downstream, from load balancers to dashboards to alert rules, treats it that way. If you put a caller's mistake in the 5xx bucket, every one of those tools now tells the wrong story.
Why a wrong 500 costs you
Returning 500 for bad input or a missing record feels harmless because the request did fail. The damage shows up in three places.
False alarms. Alert rules usually watch the 5xx rate. A crawler hitting old URLs, a mobile app sending an empty field, or a customer with a stale bookmark can all push that rate up. Your on-call gets paged, opens the logs, and finds a normal "record not found."
Alert fatigue. After a few of these, people start ignoring pages or muting the rule. Then a real outage arrives and the page gets the same shrug. The cost of a noisy alert is not the one wasted hour. It is the trust you lose in the alert.
Hidden real errors. If 5xx contains a steady background of harmless failures, a real bug adds only a small bump on top of it. Nobody can see it against the noise.
Callers also use status codes to decide whether to retry. A 500 often looks retryable, while a 400 tells the client to fix its input instead.
Mapping common situations to status codes
Here is a small mapping to start from.
- Malformed JSON, missing required field, wrong type: 400 Bad Request
- Well-formed request but the values break a business rule (for example, an end date before a start date): 422 Unprocessable Content, or 400 if you prefer one code for all validation
- No credentials, or credentials that failed: 401 Unauthorized
- Valid credentials, but the caller is not allowed to do this: 403 Forbidden
- The record or route does not exist: 404 Not Found
- The request conflicts with the current state, such as a duplicate email or an edit based on an out-of-date version: 409 Conflict
- The caller is sending too many requests: 429 Too Many Requests
- Your code threw an unexpected error: 500 Internal Server Error
- Your gateway or proxy got a bad response, or none in time, from the server behind it: 502 Bad Gateway or 504 Gateway Timeout
- Your service is overloaded or down for maintenance: 503 Service Unavailable
A few notes on the less obvious ones.
The name 401 Unauthorized is misleading. It means the caller is not authenticated: credentials are missing or failed. 403 means the server understood the request and refuses to authorize it. A logged-in user opening a page they have no rights to should get 403, not 401.
Between 400 and 422, both are valid choices for validation problems. 422 is defined as a request that is well-formed but cannot be processed because of semantic errors. Pick one convention and use it everywhere. Consistency matters more than which one you choose.
429 comes from a separate standard (RFC 6585) rather than RFC 9110, but it is widely supported and is the right code for rate limiting.
The 5xx codes have a clear meaning as well. 500 is for an unexpected condition. 502 and 504 apply when your service acts as a gateway or proxy and the upstream server gave an invalid response or did not answer in time. 503 means the service is temporarily unable to handle the request.
Handle expected failures on purpose
The usual reason for wrong 500s is that expected failures are not handled as expected failures. A query returns nothing, the code dereferences an empty result, an exception is thrown, and the framework's catch-all turns it into a 500.
Fix this at the source:
- Validate input at the edge, before it reaches business logic, and return 400 or 422 with a clear message about which field failed.
- Treat "no row found" as a normal result and return 404 deliberately.
- Catch known conflict cases, such as a unique constraint violation, and return 409 instead of letting the database error bubble up.
- Keep the catch-all handler for what it is meant for: bugs you did not predict. Anything that reaches it should be a 500, and it should be worth a look.
A useful rule: if an engineer would say "that is working as designed," it should not be a 5xx.
Set alerts on 5xx, not on 4xx
Once codes are honest, alerting gets simple.
Page on the 5xx rate. Alert on the share of requests that return 5xx over a window, not on a single error. A rate with a short window catches real outages quickly and ignores one-off blips. Tune the threshold to your own traffic instead of copying a number from a blog.
Do not page on 4xx. A 4xx is a caller problem by definition. It does not need a human at night. Send 4xx counts to a dashboard and a daily or weekly review instead.
Watch 4xx for shifts, without paging. A sudden jump in 401 or 403 can mean a broken login flow or an expired key. A jump in 404 can mean a broken link or a bad deploy. These are worth a non-urgent notification during working hours.
Separate 5xx by cause where you can. A rise in 503 or 504 points to a dependency or capacity. A rise in 500 points to your code.
Review checklist for an existing API
Use this list on an API you already run. Most of it can be done in an afternoon.
- Pull a week of 5xx responses. For each distinct error, ask: was this our bug, or a caller mistake? Anything in the second group is a wrong code.
- Send an empty body, a wrong type, and a missing field to a few write endpoints. Confirm you get 400 or 422, not 500.
- Request an ID that does not exist. Confirm 404.
- Call a protected route with no token, then with a valid token that lacks permission. Confirm 401 and then 403.
- Try to create a duplicate of something that must be unique. Confirm 409.
- Check that error responses share one format, with a message and a stable error code.
- Look at every alert rule. Confirm none fires on 4xx, and that 5xx rules use a rate and a window.
Get it right early
Correct status codes cost almost nothing at design time and are harder to fix later, because clients may depend on the old behavior. If you change a code on a live API, tell callers first.
For a second pair of eyes on your API error handling or on-call setup, our DevOps and cloud services cover exactly this kind of cleanup, or you can book a call.
Frequently Asked Questions
Need this built?
Book a free 30-minute call. We'll discuss your goals, give you honest advice, and a clear estimate — no obligation.
Ways we can help
MVP Development
Production-ready MVPs in 4-6 weeks. Fixed price, senior engineers.
Learn moreAI App Rescue
Your Lovable, Bolt, or v0 app breaks with real users? We audit, fix the gaps, and hand back code you own.
Learn moreAI Integration
Add AI features to your product — OpenAI, Claude, RAG, semantic search, and copilots.
Learn moreMobile App Development
iOS and Android from one React Native codebase. Fixed price.
Learn moreRelated articles
Why Your Date Is Off by One Day: The JavaScript Timezone Bug
A birthday or due date that shows up one day early is almost always a date-only value that got treated as a moment in time. Here is why it happens, where it hides, and how to stop it.
Read moreProcessAn AI Coding Assistant Rollout Plan for Engineering Teams
A phased plan for an AI coding assistant rollout: set goals, run a pilot, share rules files, review security, set review standards, train on real tasks, then roll out and check in.
Read moreProcessHow to Automate Repetitive Work in a Business
A practical way to find the tasks worth automating, size the saving before you build, pick the right tool, and keep people on the parts that need judgment.
Read more