How can I handle API errors?

Learning objectives:

  • Describe how {plumber} handles errors by default.
  • Send different error messages to the server than to the client.
  • Describe the possible responses from an endpoint.
  • Return useful error responses from an endpoint.

Plumber’s default error handling

#* @get /simple
function(){
  stop("I'm an error!")
}

#> {"error":["500 - Internal server error"],"message":["Error in (function () : 
#> I'm an error!\n"]}
  • HTTP status code 500 for all errors
  • Error sent to client
  • Error also printed in terminal

Custom error handling: basics

my_error_handler <- function(req, res, err) {
  res$status <- 500 # Or something else, see below.
  
  # Do something server-side (logs)
  print(err)
  
  # Return an error JSON object.
  list(error = "An error occurred. Please contact your administrator.")
}

pr("plumber.R") |> 
  pr_set_error(my_error_handler) |> 
  pr_run()

Logging strategies

Error responses

API errors are still responses

  • Status code specifies broad error category (see next slide)
    • 400 to 499 = Client errors (user did something wrong)
    • 500 to 599 = Server errors (something is broken)
    • Reminder: By default, {plumber} uses 500
  • Best practice: Error format same as successful response
    • Be careful about this, don’t make silent errors easy!
  • Need plumber::pr_handle() family for advanced options

HTTP error status codes

code title usage
400 Bad Request Missing or bad argument, etc
401 Unauthorized No or invalid authentication
403 Forbidden Known user can’t do that
404 Not Found path arg doesn’t exist
502 Bad Gateway Your api hits another api, got error
504 Gateway Timeout Your api hits another api, timed out
500 Internal Server Error Catch-all for other errors

See MDN HTTP response status codes for more options and further details

Error descriptions in plumber block

#* @response 400 Bad request. This usually occurs because of a missing or 
#*   malformed parameter.
#* @response 401 Unauthorized. A valid API token was not provided.
#* @response 403 Forbidden. This user cannot access that file.
  • @response Status Code Description
  • Can only give description (not object details)
  • This is a promise, but isn’t validated by {plumber}

Programmatic responses

TODO: Adapt/finish this slide. And probably add one more about the internal error handling in plumber.R.

  • Can specify full OpenAPI Response Object programmatically
  • Impacts auto-generated Swagger documentation
  • Not well documented (outside of this book)
pr_get(
  "/pathToEndpoint", function(res, req) { ... },
  responses = list(
    "403" = list(
      description = "Forbidden",
      content = list("application/json" = list())
    )
  )
)

Meeting Videos

Cohort 1

Meeting chat log
LOG