HTTP Status Codes Are Not Suggestions. Use Them Correctly.

by Arif Ikhsanudin, Backend Developer

When everything is 200, nothing is meaningful

If your API returns 200 OK for almost everything, you’ve effectively disabled one of the most important parts of HTTP.

This is more common than teams admit:

  • validation errors returning 200
  • permission issues returning 200
  • partial failures hidden inside a “successful” response

It works—until it doesn’t.

Clients start layering their own logic to interpret responses. Monitoring becomes unreliable. Retries don’t behave as expected. You’ve replaced a well-defined protocol with a custom one that every consumer has to relearn.

HTTP status codes are not decoration. They are part of the contract.

Status codes drive behavior, not just meaning

The biggest misunderstanding is thinking status codes are only descriptive.

They’re not. They actively control behavior across the system:

  • HTTP clients decide whether to retry based on them
  • proxies and CDNs cache based on them
  • load balancers track health using them
  • observability tools aggregate them into error rates

If you misuse them, you’re not just confusing humans—you’re breaking infrastructure.

The minimal set you actually need

You don’t need to memorize the entire RFC. Most APIs can operate correctly with a small, consistent subset.

Success (2xx)

  • 200 OK → standard success with a response body
  • 201 Created → resource successfully created
  • 204 No Content → success with no response body (e.g., delete)

Example:

HTTP/1.1 204 No Content

No JSON payload needed.

Client errors (4xx)

These indicate the client did something wrong.

  • 400 Bad Request → malformed or invalid input
  • 401 Unauthorized → missing/invalid authentication
  • 403 Forbidden → authenticated but not allowed
  • 404 Not Found → resource doesn’t exist
  • 409 Conflict → state conflict (duplicate, version mismatch)
  • 422 Unprocessable Entity → valid structure, invalid semantics

Example:

HTTP/1.1 422 Unprocessable Entity
{
  "error": {
    "code": "INVALID_QUANTITY",
    "message": "Quantity must be greater than 0"
  }
}

Server errors (5xx)

These mean your system failed to handle a valid request.

  • 500 Internal Server Error → generic failure
  • 502 Bad Gateway → upstream dependency failed
  • 503 Service Unavailable → temporary overload or maintenance

These should trigger alerts. If they don’t, your monitoring is broken.

Common misuse patterns (and why they hurt)

Using 400 for everything

HTTP/1.1 400 Bad Request

for:

  • missing auth
  • invalid permissions
  • resource not found

This flattens all client errors into one category. Clients can’t respond intelligently.

A 404 might be handled silently. A 401 might trigger re-authentication. A 403 might show a permission error.

You’ve removed that nuance.

Returning 500 for client mistakes

If a user submits invalid data and you return 500, you’re blaming the system for a client issue.

This:

  • pollutes error metrics
  • triggers unnecessary alerts
  • hides real server problems

Returning 200 with embedded errors

{
  "success": false,
  "error": "Something failed"
}

This breaks:

  • retry mechanisms
  • caching behavior
  • monitoring accuracy

You’ve effectively opted out of HTTP.

Overusing 204

204 No Content is useful, but overusing it removes useful feedback.

Example:

DELETE /orders/123

Returning 204 is fine.

But for operations where clients benefit from confirmation data, returning 200 with a body is often better:

{
  "status": "cancelled",
  "cancelled_at": "2026-04-19T10:00:00Z"
}

Status codes and idempotency

Status codes also interact with idempotency (whether repeating a request produces the same result).

Example:

DELETE /orders/123

First request:

204 No Content

Second request:

404 Not Found

Both are correct. But they signal different states:

  • first → resource existed and was deleted
  • second → resource is already gone

Clients can use this to decide whether to retry or ignore.

If both returned 200, that signal disappears.

Make status codes predictable

The real goal is not perfection. It’s predictability.

For a given type of operation, clients should know what to expect.

Example:

  • create → 201 or 409
  • fetch → 200 or 404
  • update → 200/204 or 404/422
  • delete → 204 or 404

If different endpoints handle similar situations differently, you’ve created inconsistency at the protocol level.

Testing and enforcement

If you don’t enforce status code usage, it will drift.

Add checks at multiple levels:

Contract tests

expect(response.status).toBe(422)

Not just payload validation.

OpenAPI definitions

Explicitly define responses:

responses:
  '200':
    description: Success
  '404':
    description: Resource not found
  '422':
    description: Validation error

Linting

Use tools like Spectral to enforce:

  • allowed status codes per operation
  • consistency across endpoints

This turns conventions into guarantees.

The tradeoff: strictness vs speed

Being precise with status codes takes effort:

  • developers need to think through edge cases
  • more branches in handler logic
  • more detailed tests

But skipping that work creates hidden costs:

  • unreliable retries
  • misleading metrics
  • fragile client integrations

You’re either explicit in your API design, or implicit in your bugs.

What to do differently this week

Pick one endpoint and map out all its outcomes:

  • success
  • validation failure
  • not found
  • permission issue
  • server failure

Assign the correct status code to each.

Then update the implementation and tests to enforce it.

It’s a small change, but it immediately improves how your system behaves under real conditions.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why Cheap Contractors End Up Costing Clients More

The lowest rate is rarely the lowest cost. Clients who have learned this the hard way spend more carefully the next time.

Read more

Stop Letting Every Service Handle Its Own Security

When every team implements security independently, you get inconsistent posture, duplicated effort, and gaps nobody notices until an incident. Security in microservices requires centralized enforcement at the platform layer, not per-team re-implementation.

Read more

Window Functions: The SQL Feature That Changes How You Think About Data

Window functions let you compute aggregations across a set of related rows without collapsing them — once you understand the OVER clause, you stop writing self-joins and correlated subqueries to answer questions about relative position, running totals, and row-by-row comparisons.

Read more

Burnout in Software Engineering Looks Different Than You Expect

Engineering burnout rarely announces itself as exhaustion. It shows up as detachment, cynicism about work that used to matter, and a grinding decline in the quality of your judgment — often months before you recognize what's happening.

Read more