Ctrl + K
API20 min read

API Error Response Best Practices

Learn how to design clear, consistent, secure, and useful error responses for REST APIs.

Published: 2026-10-05

API errors are unavoidable. Requests can contain invalid data, authentication can fail, resources can disappear, permissions can be insufficient, external services can become unavailable, and unexpected server errors can occur. A well-designed API does not try to eliminate every error; it makes errors predictable and useful when they happen.

A good error response should tell the client what happened, provide a stable way to identify the error, use an appropriate HTTP status code, and expose enough information for the client or developer to respond correctly. At the same time, it should avoid leaking implementation details, secrets, stack traces, database information, or other sensitive data.

Error handling becomes especially important as an API grows. If every endpoint returns a different error structure, client applications need endpoint-specific logic for common situations. Consistent error responses make APIs easier to consume, debug, test, document, and maintain.

What Is an API Error Response?

An API error response is an HTTP response that tells a client that a request could not be completed as expected. The response normally contains an HTTP status code and may include a structured body with additional information about the problem.

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": {
    "code": "resource_not_found",
    "message": "The requested user was not found."
  }
}

The HTTP status code provides a standardized high-level description of the result, while the response body can provide application-specific details. Both parts are important: clients can use the status code for general control flow and the structured body for more specific handling.

Why Consistent Error Responses Matter

Consistency is one of the most important properties of an API error format. If one endpoint returns an error field, another returns message directly, and another returns a completely different object, clients have to understand several unrelated formats.

A consistent format allows frontend applications, SDKs, logging systems, monitoring tools, and other clients to process errors in a predictable way.

ProblemBenefit of consistency
Different response structuresClients can use common error-handling code.
Unstable error identifiersApplications can reliably distinguish error types.
Inconsistent validation errorsForms can map field errors more easily.
Different status-code conventionsClient logic becomes easier to understand.
Poor documentationDevelopers spend less time guessing API behavior.

Use HTTP Status Codes Correctly

The HTTP status code should communicate the general outcome of the request. The response body should not be used as a replacement for correct HTTP semantics.

StatusTypical API use
400 Bad RequestThe request is invalid or cannot be processed because of malformed input.
401 UnauthorizedAuthentication is missing or invalid.
403 ForbiddenThe client is authenticated but is not allowed to perform the operation.
404 Not FoundThe requested resource does not exist or is not available.
405 Method Not AllowedThe HTTP method is not supported for the target resource.
409 ConflictThe request conflicts with the current state of a resource.
422 Unprocessable ContentThe request is syntactically valid but contains semantically invalid data.
429 Too Many RequestsThe client has exceeded a rate limit.
500 Internal Server ErrorThe server encountered an unexpected condition.
502 Bad GatewayA gateway or proxy received an invalid response from an upstream service.
503 Service UnavailableThe service is temporarily unable to handle the request.
504 Gateway TimeoutA gateway or proxy did not receive a timely upstream response.

The exact choice between similar status codes depends on the API's semantics and framework conventions. The important principle is that clients should be able to rely on the status code for broad error handling instead of receiving 200 OK for every application-level failure.

Do Not Return 200 for Every Error

Some APIs return HTTP 200 even when an operation failed and put the actual error inside the JSON body. Although this can work technically, it makes HTTP-level behavior less meaningful and complicates generic clients, monitoring, caching, proxies, and debugging.

{
  "success": false,
  "error": "User not found"
}

If the requested resource does not exist, returning 404 is generally more informative than returning 200 with a success flag set to false. The response body can still contain additional application-specific information.

Use a Stable Error Code

Human-readable messages can change. They may be rewritten for clarity, translated into another language, or adjusted without changing the underlying error. Client applications should therefore avoid using the message text as their primary error identifier.

{
  "error": {
    "code": "email_already_registered",
    "message": "An account with this email already exists."
  }
}

The code should be stable and machine-readable. A frontend can check email_already_registered and display the appropriate UI without depending on the exact wording of the message.

💡 Treat error codes as part of the API contract. Changing an error code can break clients just as changing a response field can.

Separate Error Codes from HTTP Status Codes

HTTP status codes and application error codes serve different purposes. The status code describes the HTTP-level result, while the application code can identify a more specific business condition.

LayerExamplePurpose
HTTP status409Indicates a conflict at the HTTP/API level.
Application codeemail_already_registeredIdentifies the specific business error.
MessageAn account with this email already exists.Provides human-readable context.

Several different application errors may legitimately share the same HTTP status. For example, multiple business conflicts can return 409 while using different error codes in the response body.

Keep the Error Structure Predictable

A useful error response should have a predictable structure. There is no single JSON format that every API must use, but the chosen format should be applied consistently.

{
  "error": {
    "code": "resource_not_found",
    "message": "The requested project was not found.",
    "requestId": "req_12345"
  }
}

A more complex API may include fields for validation details, documentation URLs, error categories, or metadata. The important part is that clients can predict where common information will appear.

Should the Error Object Be Nested?

An API can place error properties directly at the top level or nest them inside an error object. Both approaches are valid. What matters most is consistency across endpoints.

{
  "code": "resource_not_found",
  "message": "Project not found."
}

A nested error object can make the distinction between successful payload data and error metadata explicit, especially when the API response format becomes more complex.

Provide Human-Readable Messages

Error messages should explain the problem in language that developers and, where appropriate, end users can understand. A message such as 'Invalid request' is often less useful than a message that identifies what is wrong.

Less usefulMore useful
Invalid request.The expiration date must be later than the start date.
Request failed.The requested project was not found.
Error.The email address is already registered.
Bad input.The age field must contain a value between 18 and 120.

Messages should still avoid exposing internal implementation details. A client does not need to know the exact SQL query, internal exception type, filesystem path, or database host that caused a failure.

Validation Errors

Validation errors are among the most common API errors. They occur when a request has the correct general structure but contains values that do not satisfy the API's rules.

{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "fields": {
      "email": "Enter a valid email address.",
      "age": "Age must be at least 18."
    }
  }
}

Field-level validation information is especially useful for frontend applications. Instead of displaying one generic message, the UI can associate each error with the corresponding input.

For more complex validation, an array can represent multiple errors for the same field or include structured metadata such as a field name, rule, rejected value category, or location.

{
  "error": {
    "code": "validation_failed",
    "message": "The request contains invalid fields.",
    "details": [
      {
        "field": "email",
        "code": "invalid_format",
        "message": "Enter a valid email address."
      },
      {
        "field": "age",
        "code": "minimum",
        "message": "Age must be at least 18."
      }
    ]
  }
}

Do Not Return Sensitive Input Values

Validation responses should be careful about echoing the rejected input. Some fields can contain passwords, access tokens, payment information, personal data, or other secrets.

⚠️ Never include passwords, API keys, access tokens, session tokens, or other secrets in an error response simply because the corresponding request field failed validation.

Even apparently harmless debugging fields can become sensitive when they contain complete request payloads. Log and response policies should explicitly define which values may be exposed.

Authentication Errors: 401 vs 403

A common API mistake is confusing authentication and authorization errors. HTTP 401 is generally used when authentication is missing or invalid. HTTP 403 is used when the server understands the client's identity but the client is not permitted to perform the requested operation.

StatusTypical meaning
401 UnauthorizedThe client needs valid authentication credentials.
403 ForbiddenThe client is authenticated but lacks permission.

The distinction helps clients decide whether they should authenticate again, refresh credentials, request additional permissions, or simply show an access-denied message.

Resource Not Found

A 404 response is appropriate when the requested resource cannot be found. The error body can provide a stable application code and a useful message.

{
  "error": {
    "code": "user_not_found",
    "message": "The requested user was not found."
  }
}

Some APIs intentionally return similar responses for resources that exist but should not be revealed to the caller. This can prevent information disclosure, especially when resource existence itself is sensitive.

Conflict Errors

HTTP 409 Conflict can be useful when a request cannot be completed because it conflicts with the current state of a resource.

{
  "error": {
    "code": "version_conflict",
    "message": "The resource was modified by another request."
  }
}

Other examples include attempting to create a resource that conflicts with an existing unique value or updating a resource using stale state. The exact use of 409 should follow the semantics of the API.

Rate-Limit Errors

When a client exceeds a configured rate limit, the API should normally return HTTP 429 Too Many Requests. The response can include a stable error code and retry information.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Try again later."
  }
}

Clients should respect Retry-After when it is provided and avoid immediately retrying a request that was rejected because of rate limiting.

Server Errors

Unexpected server-side failures should not expose internal exception details to API consumers. The client generally needs to know that the server could not complete the request, not the internal stack trace that explains why.

{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred.",
    "requestId": "req_8f72a1"
  }
}

A request ID can be particularly useful here. The client can provide the identifier when contacting support, while server-side logs can use the same identifier to locate the corresponding failure.

Request IDs and Correlation IDs

A request ID uniquely identifies a particular API request. A correlation ID can serve a similar purpose across multiple services involved in processing a request.

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
X-Request-ID: req_8f72a1

{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred.",
    "requestId": "req_8f72a1"
  }
}

The exact header name is a design choice. What matters is that the identifier is consistent and can be connected to server-side logs and traces.

Do Not Expose Stack Traces in Production

Development environments often display detailed exception information because developers need it while debugging. Production APIs should normally return a controlled error representation instead.

{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred.",
    "requestId": "req_8f72a1"
  }
}

Detailed stack traces belong in protected server-side logs or observability systems. Returning them to clients can reveal framework versions, file paths, database queries, internal service names, and other information that may help an attacker.

Error Messages and Localization

If an API serves clients in multiple languages, error messages should not necessarily be treated as the only source of meaning. Stable error codes allow the frontend or client application to choose an appropriate localized message.

For example, an API can consistently return email_already_registered while a web application displays different text depending on the user's language.

This approach also avoids making client logic dependent on changes to server-generated prose.

Machine-Readable Errors vs Human Messages

A strong error response usually contains both machine-readable and human-readable information. The machine-readable code is intended for program logic, while the message provides useful context for developers or users.

FieldPrimary purpose
HTTP statusGeneral HTTP-level result.
Error codeStable programmatic identifier.
MessageHuman-readable explanation.
DetailsAdditional structured information.
Request IDTracing and support.

Should Clients Trust Error Messages?

Client applications should generally use stable error codes and status codes for program logic instead of matching message strings. Messages can change, be localized, or contain wording that is not intended to be machine-readable.

if (response.status === 409 && error.code === "email_already_registered") {
  showEmailAlreadyRegisteredMessage();
}

This is more robust than checking whether the response message happens to contain a particular phrase.

Avoid Overly Generic Errors

A response such as 'Something went wrong' provides little information. Generic messages are sometimes appropriate for unexpected server failures, but predictable client errors should normally contain more specific information.

GenericMore useful
Invalid data.The username must contain between 3 and 30 characters.
Not allowed.You do not have permission to update this project.
Failed.The selected payment method is no longer available.
Error.The requested document does not exist.

Avoid Excessively Detailed Errors

The opposite problem is also possible. An error response can contain so much information that it becomes difficult to understand and may expose unnecessary internal details.

A good API error exposes the information the client needs to recover or display an appropriate result. Internal implementation details should remain on the server unless there is a deliberate reason to expose them.

Use Structured Error Details

When an error requires additional context, structured fields are generally better than putting everything into a long message string.

{
  "error": {
    "code": "invalid_date_range",
    "message": "The date range is invalid.",
    "details": {
      "field": "endDate",
      "reason": "must_be_after_start_date"
    }
  }
}

Structured fields allow clients to process the information without parsing natural language.

Error Response Versioning

Error formats are part of an API contract. Changing field names, removing error codes, or changing the meaning of existing codes can break clients even when successful responses remain unchanged.

When evolving an API, preserve existing error fields and codes where possible. If a major change is necessary, document it as part of the API versioning strategy.

Adding optional fields is usually easier for clients to handle than removing or changing the semantics of existing fields.

Document Errors in OpenAPI

API documentation should describe possible error responses just as carefully as successful responses. Developers need to know which status codes can occur, what the response body looks like, and which error codes are possible.

responses:
  "400":
    description: Invalid request
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorResponse"

  "404":
    description: Resource not found
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorResponse"

  "500":
    description: Internal server error
    content:
      application/json:
        schema:
          $ref: "#/components/schemas/ErrorResponse"

OpenAPI can also define a reusable error schema so that endpoints do not need to describe the same structure independently.

Reusable Error Schemas

A shared schema makes the API contract easier to maintain. Common fields such as code, message, details, and requestId can be defined once and reused across multiple responses.

components:
  schemas:
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
            requestId:
              type: string

A reusable schema does not mean every error must contain exactly the same fields. Optional details can be added where they are useful, while the core structure remains consistent.

Testing API Error Responses

Error handling should be tested intentionally. A successful request is only one part of an API contract; clients also need predictable behavior when requests fail.

  • Send malformed JSON.
  • Omit required fields.
  • Use invalid field values.
  • Send invalid authentication credentials.
  • Attempt an operation without sufficient permissions.
  • Request a resource that does not exist.
  • Trigger known business conflicts.
  • Exceed rate limits.
  • Simulate unavailable dependencies.
  • Verify unexpected server errors return safe responses.

A REST API Mock Generator can be useful for creating predictable error scenarios during frontend development. An HTTP Response Formatter and JSON Formatter can also make structured responses easier to inspect while debugging.

Test the Error Contract, Not Just the Status Code

Testing only that an endpoint returns 400 or 404 is often insufficient. The response body is also part of the API contract.

expect(response.status).toBe(404);
expect(response.body.error.code).toBe("resource_not_found");
expect(response.body.error.message).toBeDefined();

Contract tests can verify that error structures remain stable as the API implementation changes.

Error Handling in Client Applications

A client should handle API errors according to their type rather than treating every non-success response identically. A 401 may require authentication, a 403 may require a permissions message, a 404 may indicate that a resource was removed, and a 429 may require a delayed retry.

switch (response.status) {
  case 401:
    redirectToLogin();
    break;
  case 403:
    showAccessDenied();
    break;
  case 404:
    showNotFound();
    break;
  case 429:
    scheduleRetry();
    break;
  default:
    showGenericError();
}

The application can then use the stable error code for more specific behavior when necessary.

Do Not Leak Internal Architecture

Error responses should not reveal unnecessary information about internal architecture. Examples include database table names, SQL queries, filesystem paths, private service hostnames, stack traces, framework internals, and credentials.

⚠️ Detailed debugging information belongs in protected logs and observability systems, not in production responses sent to untrusted clients.

This does not mean every production error must be completely opaque. Clients still need useful information to recover. The goal is to expose the minimum information necessary for correct client behavior.

Logging Errors on the Server

The API response and the server log serve different audiences. The response should help the client, while the log should help developers and operators investigate the failure.

Client responseServer logs
Stable error codeException details
Safe messageStack trace
Request IDRequest context
Useful validation detailsInternal diagnostic information

The two layers can be connected through a request or correlation ID. This allows support teams to start with an identifier supplied by the client and locate the corresponding internal logs.

A Recommended General Error Format

There is no mandatory JSON structure for every REST API, but the following format provides a practical foundation for many applications.

{
  "error": {
    "code": "validation_failed",
    "message": "The request contains invalid fields.",
    "details": [
      {
        "field": "email",
        "code": "invalid_format",
        "message": "Enter a valid email address."
      }
    ],
    "requestId": "req_8f72a1"
  }
}

Not every response needs every field. A simple 404 may need only code and message, while a validation error may require structured field details. Optional fields should be used when they provide meaningful information.

Error Response Design Checklist

  • Use an appropriate HTTP status code.
  • Keep the response structure consistent across endpoints.
  • Provide a stable machine-readable error code.
  • Include a clear human-readable message.
  • Use structured details instead of encoding everything in text.
  • Return field-level information for validation errors.
  • Avoid exposing secrets and sensitive input values.
  • Do not expose stack traces or internal implementation details.
  • Use request or correlation IDs for troubleshooting.
  • Document possible errors in OpenAPI.
  • Test both the status code and response body.
  • Keep error codes stable when evolving the API.
  • Make clients handle common errors such as 401, 403, 404, 409, 422, and 429 appropriately.

Frequently Asked Questions

What should an API error response contain?

A practical error response usually contains an appropriate HTTP status code, a stable machine-readable error code, a human-readable message, and optional structured details such as validation errors or a request ID.

Should API errors use JSON?

JSON is a common choice for REST APIs because it is easy for web clients and other applications to parse. The important requirement is not JSON itself but having a consistent, documented, machine-readable error format.

Should the error message be used by frontend code?

Frontend logic should normally rely on the HTTP status and stable error code rather than matching message strings. Messages can change or be localized without changing the underlying error.

What is the difference between 401 and 403?

A 401 response generally indicates missing or invalid authentication, while 403 indicates that the server knows the client's identity but the client is not permitted to perform the requested operation.

Should API errors include stack traces?

Production API responses should normally not include stack traces. Detailed exception information should be stored in protected server-side logs or observability systems instead.

How should validation errors be returned?

Validation errors should identify the general validation failure and, when useful, provide structured field-level details. This allows clients to associate errors with specific inputs without parsing human-readable text.

Should every API error have a unique error code?

Predictable error conditions should generally have stable application-level codes. The codes allow clients to distinguish specific conditions while the HTTP status communicates the broader result.

Helpful API Tools

An HTTP Response Formatter and JSON Formatter are useful for inspecting and formatting structured API error responses during development. HTTP Status Codes Lookup helps verify the semantics of statuses such as 400, 401, 403, 404, 409, 422, 429, and 500. A REST API Mock Generator can simulate validation failures, authentication errors, missing resources, and server failures without requiring the real backend to produce every scenario. For API contracts and reusable error schemas, an OpenAPI Viewer can make documented responses easier to inspect.

Conclusion

A well-designed API error response is predictable, machine-readable, useful, and safe. The HTTP status code should communicate the general outcome, while a stable error code and structured response body provide the application-specific information clients need.

Good error handling also means distinguishing validation, authentication, authorization, missing resources, conflicts, rate limits, and server failures instead of treating every failure as the same generic error. Validation details should be structured, while sensitive data and internal debugging information should remain protected.

Finally, error responses should be treated as part of the API contract. Document them in OpenAPI, test their structure, keep error codes stable, and provide request identifiers for troubleshooting. These practices make APIs easier to integrate, debug, maintain, and evolve as the system grows.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.