DEV Community

Cover image for Code Smell 302 - Misleading Status Codes
Maxi Contieri
Maxi Contieri

Posted on

2 2 1

Code Smell 302 - Misleading Status Codes

When your API says "Everything is fine!" but returns errors

TL;DR: Returning a successful HTTP status when the actual result contains an error confuses the API consumers.

Problems 😔

  • Status code confusion
  • Debugging difficulty
  • Client error handling
  • API contract violation
  • Human text parsing instead of code checking
  • Inconsistent behavior
  • The Least surprise principle violation

Solutions 😃

  1. Match status to content
  2. Use proper error codes
  3. Follow HTTP standards
  4. Implement consistent responses
  5. Test status codes
  6. Separate metadata from payload
  7. Avoid mixing success and errors
  8. Define a clear contract

Context 💬

You build an API that processes requests successfully at the HTTP transport level but encounters application-level errors.

Instead of returning appropriate HTTP error status codes such as 400 (Bad Request) or 500 (Internal Server Error), you return 200 OK with error information in the response body.

This creates a disconnect between what the HTTP status indicates and what happened, making it harder for clients to handle errors properly and for monitoring systems to detect issues.

Sample Code 📖

Wrong ❌

use axum::{
  http::StatusCode,
  response::Json,
  routing::post,
  Router,
};
use serde_json::{json, Value};

async fn process_payment(
  Json(payload): Json<Value>
) -> (StatusCode, Json<Value>) {
  let amount = payload.get("amount")
    .and_then(|v| v.as_f64());

  if amount.is_none() || amount.unwrap() <= 0.0 {
    return (
      StatusCode::OK, // Wrong: returning 200 for error
      Json(json!({"error": true, "message": "Invalid amount"}))
    );
  }

  if amount.unwrap() > 10000.0 {
    return (
      StatusCode::OK, // Wrong: returning 200 for error 
      Json(json!({"error": true, "message": "Amount too large"}))
    );
  }

  // Simulate processing error
  if let Some(card) = payload.get("card_number") {
    if card.as_str().unwrap_or("").len() < 16 {
      return (
        StatusCode::OK, // Wrong: returning 200 for error
        Json(json!({"error": true, "message": "Invalid card"}))
      );
    }
  }

  (
    StatusCode::OK, // THIS the only real 200 Status
    Json(json!({"success": true, "transaction_id": "12345"}))
  )
}

pub fn create_router() -> Router {
  Router::new().route("/payment", post(process_payment))
}
Enter fullscreen mode Exit fullscreen mode

Right 👉

use axum::{
  http::StatusCode,
  response::Json,
  routing::post,
  Router,
};
use serde_json::{json, Value};

async fn process_payment(
  Json(payload): Json<Value>
) -> (StatusCode, Json<Value>) {
  let amount = payload.get("amount")
    .and_then(|v| v.as_f64());

  if amount.is_none() || amount.unwrap() <= 0.0 {
    return (
      StatusCode::BAD_REQUEST, // Correct: 400 for bad input
      Json(json!({"error": "Invalid amount provided"}))
    );
  }

  if amount.unwrap() > 10000.0 {
    return (
      StatusCode::UNPROCESSABLE_ENTITY, 
      // Correct: 422 for business rule
      Json(json!({"error": "Amount exceeds transaction limit"}))
    );
  }

  // Validate card number
  if let Some(card) = payload.get("card_number") {
    if card.as_str().unwrap_or("").len() < 16 {
      return (
        StatusCode::BAD_REQUEST, 
        // Correct: 400 for validation error
        Json(json!({"error": "Invalid card number format"}))
      );
    }
  } else {
    return (
      StatusCode::BAD_REQUEST, 
      // Correct: 400 for missing field
      Json(json!({"error": "Card number is required"}))
    );
  }

  // successful processing
  (
    StatusCode::OK, 
    // Correct: 200 only for actual success
    Json(json!({"transaction_id": "12345", "status": "completed"}))
  )
}

pub fn create_router() -> Router {
  Router::new().route("/payment", post(process_payment))
}
Enter fullscreen mode Exit fullscreen mode

Detection 🔍

[X] Semi-Automatic

You can detect this smell when you see HTTP 200 responses that contain error fields, boolean error flags, or failure messages.

Look for APIs that always return 200 regardless of the actual outcome.

Check if your monitoring systems can properly detect failures and use mutation testing.

if they can't distinguish between success and failure based on status codes, you likely have this problem.

You can also watch client-side bugs caused by mismatched expectations.

Exceptions 🛑

  • Breaking Changes on existing API clients may require a breaking change to fix this smell.

Tags 🏷️

  • Exceptions

Level 🔋

[X] Intermediate

Why the Bijection Is Important 🗺️

HTTP status codes exist to provide a standardized way to communicate the outcome of requests between systems.

When you break this correspondence by returning success codes for failures, you create a mismatch between the HTTP protocol's semantic meaning and your application's actual behavior.

This forces every client to parse response bodies to determine success or failure, making error handling inconsistent and unreliable.

Monitoring systems, load balancers, and proxies rely on status codes to make routing and health decisions - misleading codes can cause these systems to make incorrect assumptions about your API's health.

Coupling your decisions to an incorrect status code will break the MAPPER.

Modeling a one-to-one relationship between the HTTP status code and the actual business result ensures clarity and predictability. When a 200 OK returns an internal error, the client assumes everything is fine, leading to silent failures and incorrect behaviors downstream.

By maintaining this bijection , we ensure that developers and systems interacting with the API can trust the response without additional checks.

AI Generation 🤖

AI code generators often create this smell when developers ask for "simple API examples" without specifying proper error handling.

The generators tend to focus on the happy path and return 200 for all responses to avoid complexity.

When you prompt AI to create REST APIs, you must explicitly request proper HTTP status code handling and verify the standards by yourself.

AI Detection 🥃

Many AI assistants can detect this mismatch.

Try Them! 🛠

Remember: AI Assistants make lots of mistakes

Suggested Prompt: Correct bad HTTP codes behavior

Conclusion 🏁

HTTP status codes are an important part of API design that enable proper error handling, monitoring, and client behavior.

When you return misleading status codes, you break the implicit contract that HTTP provides making your API harder to integrate with and maintain.

Always ensure your status codes accurately reflect the actual outcome of the operation.

Relations 👩‍❤️‍💋‍👨

More Information 📕

Wikipedia HTTP Codes

Disclaimer 📘

Code Smells are my opinion.


The best error message is the one that never shows up

Thomas Fuchs


This article is part of the CodeSmell Series.

Redis image

Short-term memory for faster
AI agents

AI agents struggle with latency and context switching. Redis fixes it with a fast, in-memory layer for short-term context—plus native support for vectors and semi-structured data to keep real-time workflows on track.

Start building

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.

Feature flag article image

Create a feature flag in your IDE in 5 minutes with LaunchDarkly’s MCP server ⏰

How to create, evaluate, and modify flags from within your IDE or AI client using natural language with LaunchDarkly's new MCP server. Follow along with this tutorial for step by step instructions.

Read full post

Announcing the First DEV Education Track: "Build Apps with Google AI Studio"

The moment is here! We recently announced DEV Education Tracks, our new initiative to bring you structured learning paths directly from industry experts.

Dive in and Learn

DEV is bringing Education Tracks to the community. Dismiss if you're not interested. ❤️