Providing error response in RESTful API. Part 2

Last time we talked about how a proper error response should be structured. Now let’s analyze when we should use each of the HTTP status codes.

401 Unauthorized and 403 Forbidden

There’s some confusion about these codes because 401 Unauthorized is actually intended for “unauthenticated”, i.e. user cannot log in, error while 403 Forbidden is what we usually call “unauthorized”, i.e. not enough permissions.

For 401 I distinguish two cases:

  • Username or password invalid
  • Session expired

Example responses:

{
  "status": 401,
  "code": "bad_credentials",
  "message": "Username or password invalid",
  "solution": "Check provided credentials or register a new user"
}

{
  "status": 401,
  "code": "session_expired",
  "message": "Your session token is expired",
  "solution": "Log in with your username and password"
}

403 is a very common error which is usually thrown by security layer. It should be returned when a user lacks permission to perform requested action. I suggest adding two fields in the response:

  1. permission — lacking permission name
  2. logged_in — is user logged in or not, as problem may be caused by user being unauthenticated

Example responses:

{
  "status": 403,
  "code": "access_denied",
  "permission": "create_contact",
  "logged_in": true,
  "message": "Access denied: you don't have necessary permission to perform this action",
  "solution": "Ask the administrator for appropriate permission"
}

{
  "status": 403,
  "code": "access_denied",
  "permission": "create_contact",
  "logged_in": false,
  "message": "Access denied: you must be logged in to perform this action",
  "solution": "Log in with your username and password"
}

There’s one edge case though. Consider an intruder is scanning our private contacts API GET /api/contact/ID with different ids. We should not give him a hint whether an ID exists or not. That’s why I advise to return 404 instead of 403 if a resource requested by URL is not authorized.

400 Bad request

This is the largest group as it covers multiple problems with invalid data provided by client. Let’s consider most common.

Missing required request parameter

When a required query or path parameter value is not specified.

{
  "status": 400,
  "code": "missing_parameter",
  "parameter_name": "contact_id",
  "message": "Required request parameter value is missing: request_id",
  "solution": "Provide the request_id value"
}

Record not found

This should be thrown when some complex request references non-existing entity with a request parameter or in request body, e.g. GET /api/income-report?company_id=100&contact_id=123

{
  "status": 400,
  "code": "record_not_found",
  "record_type": "contact",
  "record_id": 123,
  "message": "A contact with id 123 not found",
  "solution": "Check the id provided or create a new record"
}

If we throw 404 a client may think that he misspelled income-report URL.

But if a client requests a specific record by a direct URL like GET /api/contacts/123 we should throw 404 Not found instead.

Validation error

Different validation errors occurring in business layer when requested parameters are processed. A specific message should be provided in each case.

{
  "status": 400,
  "code": "invalid_property_value", 
  "property": "age",
  "message": "Age must be a positive number",
  "solution": "Check value provided"
}

Invalid JSON

There could be syntax error in request JSON which should also be covered with a proper response.

404 Resource not found

When a non-existing entity is requested by a direct link we should return 404. I also suggest returning this status code in case a resource is non-authorized (see 403 section above)

{
  "status": 404,
  "code": "record_not_found", 
  "record_type": "contact",
  "record_id": 123,
  "message": "Requested contact with id 123 was not found",
  "solution": "Check the id provided or create a new record"
}

409 Conflict

There comes a broad range of different database conflicts.

Duplicate id

Thrown when an attempt to create an entity with existing unique id is made.

{
  "status": 409,
  "code": "duplicate_id", 
  "record_type": "contact",
  "record_id": 123,
  "message": "A contact with id 123 already exists",
  "solution": "Provide another id value"
}

Foreign key violation

An attempt to violate foreign key reference, e.g. delete a referenced record or reference a non-existing record.

{
  "status": 409,
  "code": "foreign_key_violation", 
  "record_type": "contact",
  "record_id": 123,
  "referenced_type": "company",
  "referenced_id": 25,
  "message": "Cannot delete contact#123 as it is still referenced by company#25",
  "solution": "Delete company#25 first"
}

This is just an example as there could be many different types of constraint violations.

402 Payment Required — an exotic one

This code is not widely used but it could be used in case when for example a user wants to perform an action not covered by his subscription plan. Thus this action could be performed after the plan is upgraded.

{
  "status": 402,
  "code": "listen_limit_exceeded", 
  "message": "Limit of 1000 audio records per day is exceeded",
  "solution": "Upgrade your subscription"
}

You could also use 409 Conflict in this case but it usually implies you should do something with the data, not with the money 🙂

5xx Server errors

And finally to the server errors.

As you always write high quality server code (aren’t you?) I cannot imagine a situation when you would throw a server error explicitly (your HTTP server could of course return 503 Service Unavailable).

The only server code I use is 502 Bad Gateway. It is helpful if our server is interacting with some external API (e.g. Facebook / Twitter) thus serving as a gateway. If we just want to resend external API error to our client without processing it we can return 502 server error code.

Next time I will show how to implement such proper error handling in Spring web application with a minimal effort.

You could also check the previous article for general ideas on proper error handling in REST architecture.

Links

  1. Providing error response in RESTful API. Part 1
  2. Providing error response in RESTful API. Part 2
  3. Providing error response in RESTful API. Part 3

Published by Andrey Minogin

Full-stack Web/Java developer

Leave a comment

Your email address will not be published. Required fields are marked *