How can I get inputs from API users?

Learning objectives:

  • Decide whether API inputs should be passed in the path, query, body, header, or cookie.
  • Process API arguments directly from the request object.
  • Process API arguments specified in the path.
  • Process API arguments passed in the query string.
  • Process API arguments passed in the request body.
  • Process API arguments sent as a header.
  • Process API arguments stored in a cookie.

Path vs Other

  • Path for main noun (the thing you’re acting on)
  • Query/body for arguments
  • Cookies/header for metadata, auth, etc

Types of inputs

How will your API be used?

  • Directly in browser (Are you sure?)
    • GET
    • Query for things user decides
    • Cookies for state, etc
  • Mostly programmatic (including from Shiny, etc)
    • POST, PUT, PATCH, DELETE
    • Body for things related to this specific request
    • Header for things that aren’t function arguments
    • Possibly cookies for state still

API function arguments

  • Can expect named arguments, BUT
  • Better: Accept req (request object)
  • Specify priority (or only accept certain ones)
    • Good documentation MUCH better than flexibility!
    • By default: argsQuery > argsPath > argsBody
function(req) {
  x <- req$argsPath$x %||% req$argsBody$x %||% req$argsQuery$x %||% 
    req$cookies$x %||% req$HTTP_X
}

Parameter types

  • Optionally specify type (~class) in API definition
    • @param x:int (more details later)
  • Enclose in [] to allow length > 1 (array)
    • @param x:[int]

Parameter types table

type R class in
“boolean” logical query, path
“number” numeric query, path
“integer” integer query, path
“string” character query, path
“object” list body
“file” raw body
  • All of these have other aliases (eg int for integer)
  • Stick with these for smooth experience

Path parameters

#* @get /users/<id>
function(id) {...} # better: use req$argsPath$id

Optional: only route certain types

#* @get /users/<id:int>
function(id) {...} # better: use req$argsPath$id

{plumber} casts typed path input

Query parameters

https://example.com/api/repeat?letter=x&number=1

#* @get repeat
#* @param letter:string The letter.
#* @param number:integer The number of times to repeat it.

{plumber} does not cast query input!

Request bodies

  • req$bodyreq$bodyArgs
    • body sometimes has raw value + parsed, other details
  • Parsers can prepare body
    • Specify as #* @parser PARSER (json, csv, rds, etc)
    • Args in list: #* @parser json list(simplifyVector = FALSE)
#* @param df:object The input data.frame. Must contain columns "first_name" 
#*        and "city".
#* @parser feather list(col_select = c("first_name", "city"))

Stabilizing input

TODO: {stbl} package introduction once it’s stable.

TODO: Also talk about DoS avoidance (input limits) + Sanitation

HTTP headers

Headers are processed and included in req object:

  • - replaced with _
  • UPPERCASE
  • HTTP_ prefix

X-custom-headerreq$HTTP_X_CUSTOM_HEADER

Inputs in Cookies

  • req$cookies = named list of (unencrypted) cookies
  • req$session = named list from encrypted cookies
    • Much more info when we save cookies

Remember filters

TODO: Add slide about dealing with things that aren’t specific to a given function via filters (eg, for auth).

Meeting Videos

Cohort 1

Meeting chat log
LOG