1. Library
  2. Http and the Web
  3. Status Codes

Updated 10 hours ago

The 400 Bad Request error is the server extending a hand: "I'm listening, I want to process your request, but I can't understand what you're saying."

Unlike 5xx errors (the server broke) or 401/403 (you're not allowed), a 400 means the conversation failed before it could begin. The server received your request, tried to make sense of it, and couldn't. The fix is always on the client side.

What Triggers a 400

The request itself is broken. The server can't even figure out what you're asking for.

Malformed JSON—the most common cause:

POST /api/users HTTP/1.1
Content-Type: application/json

{
    "name": "Alice"
    "email": "alice@example.com"
}

That missing comma after "Alice" means the server receives garbage instead of data. It's not that your values are wrong—the server can't even see your values.

Missing required fields:

POST /api/users HTTP/1.1
Content-Type: application/json

{"name": "Alice"}

If the API requires an email field and you don't send one, the server can't proceed. It's like filling out a form but leaving the signature line blank.

Wrong types:

{"age": "twenty-five"}

When the server expects a number and receives a string, it can't safely guess what you meant. Did you mean 25? 20? The server refuses to assume.

Invalid values:

{"email": "not-an-email"}

The structure is right, but the content doesn't pass validation. An email field that doesn't contain an email address is a request the server can't fulfill.

400 vs. Other Error Codes

Choosing the right error code tells clients what went wrong and what they can do about it.

400 vs. 401: Use 400 when the request structure is broken. Use 401 when the request is fine but the user isn't authenticated. "Your JSON is invalid" is 400. "You need to log in" is 401.

400 vs. 403: Use 400 for malformed requests. Use 403 when the request is valid but the user doesn't have permission. "Invalid email format" is 400. "You can't delete other users' accounts" is 403.

400 vs. 404: Use 400 when the request itself is wrong. Use 404 when the request is valid but the resource doesn't exist. "Invalid user ID format" is 400. "User 12345 doesn't exist" is 404.

400 vs. 422: This distinction is subtle but meaningful. 400 means "I can't parse your request"—syntax errors, malformed JSON, missing headers. 422 means "I understand your request but it doesn't make sense"—like trying to create a user with an email that already exists. The request is well-formed but semantically impossible.

In practice, many APIs use 400 for both. But if you want precision: 400 is gibberish, 422 is a logical contradiction.

Writing Useful Error Responses

A bare {"error": "Bad Request"} helps no one. The whole point of 400 is to help the client fix their request.

Tell them what's wrong:

{
    "error": "Validation failed",
    "details": [
        {"field": "email", "message": "Invalid email format"},
        {"field": "age", "message": "Must be between 0 and 150"}
    ]
}

Show them how to fix it:

{
    "error": "Invalid date format",
    "expected": "YYYY-MM-DD",
    "received": "10/21/2024",
    "example": "2024-10-21"
}

Include machine-readable codes:

{
    "error": "Validation failed",
    "code": "VALIDATION_ERROR",
    "details": [
        {"field": "email", "code": "INVALID_FORMAT"}
    ]
}

Error codes let clients handle specific cases programmatically. The human-readable message is for developers debugging; the code is for the application logic.

Server Implementation

Validate early. The moment a request arrives, check if it's valid before doing anything else:

app.post('/api/users', function(request, response) {
    const errors = [];
    
    if(!request.body.email) {
        errors.push({field: 'email', message: 'Email is required'});
    }
    else if(!isValidEmail(request.body.email)) {
        errors.push({field: 'email', message: 'Invalid email format'});
    }
    
    if(errors.length > 0) {
        return response.status(400).json({
            error: 'Validation failed',
            details: errors
        });
    }
    
    // Only reach here with valid data
    createUser(request.body);
});

Use validation libraries instead of writing checks by hand:

const Joi = require('joi');

const userSchema = Joi.object({
    name: Joi.string().required(),
    email: Joi.string().email().required(),
    age: Joi.number().min(0).max(150)
});

app.post('/api/users', function(request, response) {
    const { error, value } = userSchema.validate(request.body);
    
    if(error) {
        return response.status(400).json({
            error: 'Validation failed',
            details: error.details.map(function(d) {
                return {field: d.path.join('.'), message: d.message};
            })
        });
    }
    
    // value contains validated, sanitized data
});

Client Handling

The critical thing about 400 errors: don't retry automatically. Unlike a 503 (server temporarily overloaded), a 400 won't magically succeed on the second attempt. The request is broken. It will stay broken until someone fixes it.

async function createUser(userData) {
    const response = await fetch('/api/users', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(userData)
    });
    
    if(response.status === 400) {
        const error = await response.json();
        
        // Show the user what's wrong
        if(error.details) {
            error.details.forEach(function(detail) {
                showFieldError(detail.field, detail.message);
            });
        }
        
        // Don't retry—let the user fix the form
        throw new ValidationError(error);
    }
    
    return response.json();
}

Security Consideration

Error messages should help legitimate users fix their requests without helping attackers probe your system. "Email already exists" is fine. "Database constraint violation on users.email unique index" exposes your infrastructure.

Frequently Asked Questions About 400 Bad Request

Was this page helpful?

😔
🤨
😃