Building API requests
requests.RmdOverview
spdgt.auth provides a request pipeline for SpeedGoat
REST APIs. You build requests with api_get(),
api_post(), api_patch(), and
api_delete(), and parse responses with
parse_json2tibble() or other parsers. Authentication and
token management happen automatically.
This vignette assumes your package has already registered its API
with add_api() (see
vignette("authentication")).
Retrieving data
Basic GET request
api_get() builds and executes a GET request in one
call:
resp <- api_get("counts", "projects")
parse_json2tibble(resp)To retrieve a single record by ID, use api_get_id(). The
id must be a single non-NA value:
resp <- api_get_id("counts", "projects", id = 42)
parse_json2tibble(resp)Filtering
Use the filters argument to narrow results. Filters
translate to filter[column]=value query parameters:
# Exact match
api_get("counts", "projects/age-classes",
filters = list(species_id = 1)
)
# Multiple filters (AND)
api_get("counts", "projects/age-classes",
filters = list(species_id = 1, age_class_id = 2)
)
# Multiple values (OR) — pass a vector
api_get("counts", "projects/age-classes",
filters = list(species_id = c(1, 2, 3))
)Filters support special operators:
-
!excludes matching values:list(species_id = "!1") -
~matches partially:list(name = "~elk")matches “elk”, “Rocky Mountain Elk”, etc. -
!~excludes partial matches:list(name = "!~test")
When combining operators, ! always comes first.
Pagination
Control how many records are returned with the pages
argument:
# First 50 records
api_get("counts", "surveys",
pages = list(size = 50, number = 1)
)
# Omit pagination metadata (returns flat list)
api_get("counts", "surveys",
pages = list(omit = TRUE)
)Available page parameters: size (records per page),
number (page number), omit (skip pagination
wrapper).
Includes
Includes eagerly load related resources into the response. The available includes for each endpoint are documented in the API’s Swagger docs.
# Include species data with age classes
api_get("counts", "projects/age-classes",
includes = "species",
valid_includes = c("species", "units")
)The valid_includes parameter is optional — when
provided, the request will abort early if an invalid include is passed.
This is useful for package authors wrapping endpoints.
Included data appears as list columns in the parsed tibble.
Combining modifiers
All modifiers can be combined in a single call:
resp <- api_get("counts", "projects/age-classes",
filters = list(species_id = 1),
includes = "species",
pages = list(size = 100),
appends = "some_computed_field"
)
parse_json2tibble(resp)Creating data
Single record
api_post() sends a POST request with a JSON body. The
body should be a tibble (or data frame):
body <- tibble::tibble(
project_id = 1,
species_id = 1,
age_class = "Adult",
age_min_months = 18,
age_max_months = 30
)
resp <- api_post("counts", "projects/age-classes", body = body)
parse_json2tibble(resp)Use add_user_id = TRUE or
add_project_id = TRUE to automatically inject the
authenticated user’s ID or project ID into the body:
api_post("counts", "surveys",
body = survey_data,
add_user_id = TRUE,
add_project_id = TRUE
)Multiple records
api_post_multi() sends multiple records in a single
request using the batch endpoint pattern
(POST /api/{resource}/multiple):
body <- tibble::tibble(
project_id = c(1, 1),
species_id = c(1, 2),
age_class = c("Adult", "Juvenile")
)
resp <- api_post_multi("counts", "projects/age-classes/multiple",
body = body,
nm = "age_classes"
)Use parse_multi2tibble() to parse the response into a
tibble with data, message, and
success columns. It warns automatically when any records
fail:
parse_multi2tibble(resp)For large batch inserts, the API processes records asynchronously.
Use api_post_progress() to poll the status:
result <- parse_json2list(resp)
api_post_progress("counts", key = result$key)Data frames
api_post_df() is an alternative for posting multi-row
data frames directly:
api_post_df("counts", "projects/age-classes/multiple",
body = my_dataframe,
nm = "age_classes"
)File uploads
api_post_file() uploads a file with auxiliary metadata
using multipart form encoding:
api_post_file("counts", "geography/import",
file_path = "regions.geojson",
aux_data = list(project_id = "1", type = "region")
)Updating data
api_patch() updates an existing record by ID. The
id must be a single non-NA value, and the body must be a
single-row tibble containing only the fields to update:
updates <- tibble::tibble(age_class = "Sub-Adult")
api_patch("counts", "projects/age-classes",
id = 42,
body = updates
)api_patch_multi() updates multiple records in one
request. The id vector must have no NA values and its
length must match the number of rows in body:
api_patch_multi("counts", "projects/age-classes/multiple",
id = c(42, 43),
body = tibble::tibble(age_class = c("Sub-Adult", "Juvenile"))
)Deleting data
api_delete() removes a record by ID and returns the
parsed response. The id must be a single non-NA value:
api_delete("counts", "projects/age-classes", id = 42)Exporting data
api_export() triggers a server-side export in CSV, JSON,
or Parquet format. The response contains a temporary download URL:
resp <- api_export("counts", "surveys/export",
format = "parquet",
filters = list(project_id = 1)
)
# Print download URL and file details
parse_url(resp)Exports support the same filters, includes, appends, and pagination
as api_get().
Parsing responses
JSON to tibble
parse_json2tibble() is the primary parser. It extracts
the data element, applies schema-based type conversion, and
returns a tibble:
resp <- api_get("counts", "projects")
projects <- parse_json2tibble(resp)Key arguments:
-
df = TRUE(default): Expects a structured response with adataelement and schema. Set toFALSEfor simpler responses. -
validate = TRUE(default): Applies schema-based type conversion. Set toFALSEto skip validation. -
elements = "data"(default): Which element to extract from the response.
Included relationships appear as list columns in the result.
JSON to list
For responses where a tibble isn’t appropriate:
result <- parse_json2list(resp)
result$data
result$messageExport URLs
parse_url() prints download details and invisibly
returns the URL:
url <- parse_url(resp)
# Use the URL to download the fileBuilding wrapper functions
When building a downstream package, wrap these primitives into domain-specific functions:
#' Get age classes for a project
#'
#' @param species_id Filter by species
#' @param ... Additional arguments passed to `api_get()`
#' @return A tibble of age classes
#' @export
get_age_classes <- function(species_id = NULL, ...) {
filters <- list()
if (!is.null(species_id)) {
filters$species_id <- species_id
}
spdgt.auth::api_get(
api_name = "counts",
endpoint = "projects/age-classes",
filters = filters,
includes = "species",
valid_includes = c("species", "units"),
...
) |>
spdgt.auth::parse_json2tibble()
}The dry_run, verbosity, and
timeout arguments pass through via ..., giving
callers control without cluttering your function signature.
Utility helpers for wrapper functions
spdgt.auth exports several helpers that simplify writing
wrapper functions in domain packages.
Validating inputs — check_enum()
validates values against an allowed set,
check_required_cols() verifies a data frame has the
expected columns, and format_timestamp() converts
POSIXct or POSIXlt values to the UTC string
format the API expects:
create_event <- function(type, occurred_at, ...) {
spdgt.auth::check_enum(type, c("capture", "mortality", "observation"))
body <- tibble::tibble(
type = type,
occurred_at = spdgt.auth::format_timestamp(occurred_at)
)
spdgt.auth::api_post("counts", "events", body = body, ...) |>
spdgt.auth::parse_json2tibble()
}Assembling filter lists — api_filters()
and api_parameters() silently drop NULL
entries, so you can pass optional arguments directly:
get_events <- function(type = NULL, species_id = NULL, ...) {
spdgt.auth::api_get("counts", "events",
filters = list(type = type, species_id = species_id),
...
) |>
spdgt.auth::parse_json2tibble()
}compact_list() is exported for cases where you need to
build or inspect a filter list before passing it. It removes NULL
entries from a list, which is how optional filters are handled
internally:
compact_list(list(species_id = 1, type = NULL, region_id = 3))