Skip to contents

Overview

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.

Appends

Appends add computed attributes to each record:

api_get("counts", "models/covars",
  appends = c("available_activities", "available_vegetation"),
  valid_appends = c("available_activities", "available_vegetation")
)

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)

Dry run

Use dry_run = TRUE to inspect the request without sending it. This prints the HTTP method, URL, and headers:

api_get("counts", "projects",
  filters = list(species_id = 1),
  dry_run = TRUE
)

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:

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")
)

Metadata

When a body includes a metadata column, it must be a named list of atomic values or data frames. Metadata cannot be nested deeper than one level:

body <- tibble::tibble(
  project_id = 1,
  species_id = 1,
  metadata = list(
    comment = "field notes",
    measurements = tibble::tibble(x = 2, y = 3)
  )
)

api_post("counts", "endpoint", body = body)

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 a data element and schema. Set to FALSE for simpler responses.
  • validate = TRUE (default): Applies schema-based type conversion. Set to FALSE to 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$message

Parquet

For parquet responses (from exports or direct endpoints):

df <- parse_parquet2df(resp)

Export URLs

parse_url() prints download details and invisibly returns the URL:

url <- parse_url(resp)
# Use the URL to download the file

Building 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 inputscheck_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 listsapi_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))