Heavily plagierized from a DZone article I read, but it's not only that article's content...
Follow HTTP expectations:
There are two acceptable ranges of status code for an error response: 4xx or 5xx.
The 4xx range is for client-side errors and the 5xx for server side. In general, a 4xx means that the client needs to do something before it tries again. Some like 401 and 403 are related to permissions while most are related to ensuring that the message is a good HTTP message. Use 400 for validation errors.
Nearly all the 5xx are infrastructure related, so in code, a 500 or 503 will be most likely returned if something goes wrong at the server end. If 503, add the Retry-After statement to the header so clients won't hammer the server. Here are two examples:
Retry-After: Fri, 31 Dec 1999 23:59:59 GMT (means not before this date and time) Retry-After: 120 (means not for 2 minutes)
The Accept statement is used by HTTP clients to tell the server what content types they'll accept. The server returns a header with a Content-Type statement saying what the actual content type of the response is. (And this should be what the client passed in Accept if at all possible.)
An error response should honor the data format indicated by the client in the Accept header sent. If it says XML, don't return JSON or HTML. Some examples:
Accept: text/plain Accept: application/xml Accept: application/json Accept-Charset: utf-8 Accept-Encoding: gzip, deflate
Similarly, ensure that the correct Content-Type header gets set describing the response for the client to use this when decoding it.
Content-Type: application/xml Content-Type: application/json
If using a standard media type for the error, then save on documentation and allow client to use a library to handle it. If using a media type with error objects defined (e.g.: JSON-API), use that.
Otherwise, use RFC7807: Problem Details for HTTP APIs. This defines both a JSON and XML format for error handling and there are libraries out there for most languages that will decode it.
In documentation, encourage clients to use an Accept header that names all the media types the server is expected to return. e.g.:
Accept: application/hal+json, application/problem+json
The code is for the client application. It should never change and allows the client to perform different actions based on the specific error returned. An application error code is required because HTTP status codes are too little granular and a client should never have to string match to work out what's going on.
The human readable error message is to help client developers as much as possible. The message should provide information on what's gone wrong and ideally give information on how to fix it.
Document all codes and messages.