Error Response Format

The default error response format looks like this

{
  "type": "validation_error",
  "errors": [
    {
      "code": "required",
      "detail": "This field is required.",
      "attr": "name"
    }
  ]
}
  • type: can be validation_error, client_error or server_error

  • code: short string describing the error. Can be used by API consumers to customize their behavior.

  • detail: User-friendly text describing the error.

  • attr: set only when the error type is a validation_error and maps to the serializer field name or settings.NON_FIELD_ERRORS_KEY.

Error Types

Validation Errors

These are ones caused by raising a rest_framework.exceptions.ValidationError. They are the only error type that can have more than 1 error. The list of corresponding error codes depends on the serializer and its fields.

Client Errors

Covers all 4xx errors other than validation errors. Here’s a reference to all possible error codes and corresponding exceptions:

Error Code

Status Code

DRF Exception

parse_error

400

ParseError

authentication_failed

401

AuthenticationFailed

not_authenticated

401

NotAuthenticated

permission_denied

403

PermissionDenied

not_found

404

NotFound

method_not_allowed

405

MethodNotAllowed

not_acceptable

406

NotAcceptable

unsupported_media_type

415

UnsupportedMediaType

throttled

429

Throttled

Server Errors

These are ones caused by raising a rest_framework.exceptions.APIException or by unhandled exceptions. The corresponding error code is error and the status code is 500.

Multiple Errors Support

Out of the box, only validation errors would result in an error response with multiple errors. The errors can be for the same field or for different fields. So, a sample DRF error dict like this:

{
    "phone": [
        ErrorDetail("The phone number entered is not valid.", code="invalid_phone_number")
    ],
    "password": [
        ErrorDetail("This password is too short.", code="password_too_short"),
        ErrorDetail("The password is too similar to the username.", code="password_too_similar"),
    ],
}

would be converted to:

{
    "type": "validation_error",
    "errors": [
        {
            "code": "invalid_phone_number",
            "detail": "The phone number entered is not valid.",
            "attr": "phone"
        },
        {
            "code": "password_too_short",
            "detail": "This password is too short.",
            "attr": "password"
        },
        {
            "code": "password_too_similar",
            "detail": "The password is too similar to the username.",
            "attr": "password"
        }
    ]
}

Nested Serializers Support

Taking this example

{
    "shipping_address": {
        "non_field_errors": [ErrorDetail("We do not support shipping to the provided address.", code="unsupported")]
    }
}

It will be converted to:

{
    "code": "unsupported",
    "detail": "We do not support shipping to the provided address.",
    "attr": "shipping_address.non_field_errors"
}

Note how the attr is the combined value of parent serializer field name and nested one. They are separated by a . by default, but that can be changed through the setting NESTED_FIELD_SEPARATOR.

List Serializers Support

This example

{
    "recipients": [
        {"name": [ErrorDetail("This field is required.", code="required")]},
        {"email": [ErrorDetail("Enter a valid email address.", code="invalid")]},
    ]
}

would be converted to

{
    "type": "validation_error",
    "errors": [
        {
            "code": "required",
            "detail": "This field is required.",
            "attr": "recipients.0.name"
        },
        {
            "code": "invalid",
            "detail": "Enter a valid email address.",
            "attr": "recipients.1.email"
        }
    ]
}

Note that distinguishing between errors in different objects in the nested list serializer is done using 0-based indexing.