Skip to main content

R Examples

These examples use the httr2 package — the modern R HTTP client with a pipeable API.


Setup

Install the required packages:

install.packages("httr2")

# For data manipulation and plotting (optional, used in later examples)
install.packages("dplyr")
install.packages("tibble")
install.packages("ggplot2")

Store your API key in an environment variable — never hardcode it in source files:

# In your .Renviron file (recommended):
# PRAIRIECLOUD_API_KEY=pck_live_YOUR_KEY_HERE

# Or set it for the current session:
Sys.setenv(PRAIRIECLOUD_API_KEY = "pck_live_YOUR_KEY_HERE")
Get your API key

Sign up for free → — Free tier includes 1,000 requests/month. Manage all your keys in the Developer Dashboard.

library(httr2)

API_BASE <- "https://api.prairiecloud.io"
API_KEY <- Sys.getenv("PRAIRIECLOUD_API_KEY")

if (nchar(API_KEY) == 0) {
stop("PRAIRIECLOUD_API_KEY environment variable is not set.")
}

Single Variable Query

Get the total population for Texas (FIPS code 48):

library(httr2)

resp <- request(paste0(API_BASE, "/v1/data")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variables = "pop_total",
geo = "state:48",
vintage = "2024"
) |>
req_perform()

body <- resp |> resp_body_json()

# Extract the value from the nested envelope: data -> geo_key -> variables -> api_name
texas <- body$data[["state:48"]]
pop <- texas$variables$pop_total$estimate

cat("Texas total population:", format(pop, big.mark = ","), "\n")
# Texas total population: 30,976,754

The response envelope structure:

body$data          # named list of geo_key → geography object
body$meta # source info, request_id, elapsed_ms
body$links # self URL

Multiple Variables

Get total population and median age for Harris County, TX (FIPS 48201):

resp <- request(paste0(API_BASE, "/v1/data")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variables = "pop_total,income_median_household",
geo = "county:48201",
vintage = "2024"
) |>
req_perform()

body <- resp |> resp_body_json()

harris <- body$data[["county:48201"]]
vars <- harris$variables

cat("Harris County —", harris$name, "\n")
cat(" Total population: ", format(vars$pop_total$estimate, big.mark = ","), "\n")
cat(" Median household income:", paste0("$", format(vars$income_median_household$estimate, big.mark = ",")), "\n")
# Harris County — Harris County, Texas
# Total population: 4,780,913
# Median household income: $61,705

Compare Endpoint

Side-by-side comparison of Texas, California, and New York:

resp <- request(paste0(API_BASE, "/v1/compare")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variables = "pop_total",
geo = "state:48,state:06,state:36",
vintage = "2024"
) |>
req_perform()

body <- resp |> resp_body_json()

# body$data is a named list: geo_key → { name, variables, ... }
for (geo_key in names(body$data)) {
geo <- body$data[[geo_key]]
pop <- geo$variables$pop_total$estimate
cat(sprintf("%-25s %s\n", geo$name, format(pop, big.mark = ",")))
}
# Texas 30,976,754
# California 39,431,263
# New York 19,677,151

Timeseries Endpoint

Track total population for Texas across all available vintages:

resp <- request(paste0(API_BASE, "/v1/timeseries")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variable = "pop_total",
geo = "state:48"
) |>
req_perform()

body <- resp |> resp_body_json()

# body$data is a list of { vintage, estimate, margin_of_error, ... }
for (point in body$data) {
cat(sprintf("Vintage %d: %s\n", point$vintage, format(point$estimate, big.mark = ",")))
}
# Vintage 2022: 29,527,941
# Vintage 2023: 30,029,572
# Vintage 2024: 30,976,754

Listing Variables

Browse available variables in the core topic:

resp <- request(paste0(API_BASE, "/v1/variables")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
topic = "core",
limit = 10
) |>
req_perform()

body <- resp |> resp_body_json()

cat(sprintf("Found %d variables (showing first %d):\n",
body$meta$total_count, length(body$data)))

for (v in body$data) {
cat(sprintf(" %-45s %s\n", v$api_name, v$label))
}
# Found 238 variables (showing first 10):
# pop_total Total population
# pop_male Male
# pop_female Female
# pop_male_under_5 Male: Under 5 years
# ...

Error Handling

Handle common API errors — invalid key, rate limit exceeded, resource not found:

safe_request <- function(url, query_params) {
req <- request(url) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(!!!query_params)

tryCatch(
{
resp <- req |> req_perform()
resp |> resp_body_json()
},
httr2_http_401 = function(e) {
stop(
"Authentication failed: check your API key in the Dashboard at ",
"https://dashboard.prairiecloud.io/dashboard/keys/",
call. = FALSE
)
},
httr2_http_429 = function(e) {
# Respect the Retry-After header
retry_after <- as.integer(e$resp$headers[["retry-after"]] %||% "60")
message(sprintf(
"Rate limited. Retry after %d seconds. ",
retry_after
))
message("Upgrade your plan: https://dashboard.prairiecloud.io/dashboard/billing/")
stop("Rate limit exceeded.", call. = FALSE)
},
httr2_http_404 = function(e) {
stop("Resource not found: check your geo or variable name.", call. = FALSE)
},
httr2_http = function(e) {
# Catch all other HTTP errors (400, 422, 500, etc.)
stop(sprintf("API error %d: %s", resp_status(e$resp), conditionMessage(e)),
call. = FALSE)
}
)
}

# Usage:
result <- safe_request(
paste0(API_BASE, "/v1/data"),
list(variables = "pop_total", geo = "state:48", vintage = "2024")
)

Retry with Exponential Backoff

For production workloads, use httr2's built-in retry support:

resp <- request(paste0(API_BASE, "/v1/data")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variables = "pop_total",
geo = "state:48"
) |>
req_retry(
max_tries = 5,
is_transient = \(resp) resp_status(resp) %in% c(429, 500, 502, 503, 504),
backoff = \(i) (2^i) + runif(1, 0, 1) # exponential backoff with jitter
) |>
req_perform()

Working with Data Frames

Convert API responses to tibbles for analysis with dplyr:

library(httr2)
library(tibble)
library(dplyr)

# Fetch population data for all three states
resp <- request(paste0(API_BASE, "/v1/compare")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variables = "pop_total,income_median_household",
geo = "state:48,state:06,state:36",
vintage = "2024"
) |>
req_perform()

body <- resp |> resp_body_json()

# Build a tibble from the nested response
states_df <- purrr::map_dfr(names(body$data), function(geo_key) {
geo <- body$data[[geo_key]]
vars <- geo$variables
tibble(
geo_key = geo_key,
name = geo$name,
population = vars$pop_total$estimate,
income = vars$income_median_household$estimate
)
})

print(states_df)
# # A tibble: 3 × 4
# geo_key name population income
# <chr> <chr> <dbl> <dbl>
# 1 state:48 Texas 30976754 73035
# 2 state:06 California 39431263 84097
# 3 state:36 New York 19677151 75157

# Sort by population descending
states_df |> arrange(desc(population))

For timeseries data, build a time series tibble:

resp <- request(paste0(API_BASE, "/v1/timeseries")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variable = "pop_total",
geo = "state:48"
) |>
req_perform()

body <- resp |> resp_body_json()

ts_df <- purrr::map_dfr(body$data, function(point) {
tibble(
vintage = point$vintage,
estimate = point$estimate,
margin_of_error = point$margin_of_error
)
})

print(ts_df)
# # A tibble: 3 × 3
# vintage estimate margin_of_error
# <int> <dbl> <dbl>
# 1 2022 29527941 NA
# 2 2023 30029572 NA
# 3 2024 30976754 NA

Plotting

Plot Texas population growth over time using ggplot2:

library(httr2)
library(tibble)
library(ggplot2)
library(scales) # for label_comma()

# Fetch timeseries data
resp <- request(paste0(API_BASE, "/v1/timeseries")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variable = "pop_total",
geo = "state:48"
) |>
req_perform()

body <- resp |> resp_body_json()

ts_df <- purrr::map_dfr(body$data, function(pt) {
tibble(vintage = pt$vintage, estimate = pt$estimate)
})

# Plot
ggplot(ts_df, aes(x = vintage, y = estimate)) +
geom_line(color = "#2563eb", linewidth = 1) +
geom_point(color = "#2563eb", size = 3) +
scale_y_continuous(labels = label_comma()) +
scale_x_continuous(breaks = ts_df$vintage) +
labs(
title = "Texas Total Population — ACS 5-Year Estimates",
subtitle = "Source: PrairieCloud API / U.S. Census Bureau ACS",
x = "Vintage Year",
y = "Population"
) +
theme_minimal()

To compare multiple states side-by-side, combine the compare endpoint with ggplot:

resp <- request(paste0(API_BASE, "/v1/compare")) |>
req_headers("X-API-Key" = API_KEY) |>
req_url_query(
variables = "pop_total",
geo = "state:48,state:06,state:36",
vintage = "2024"
) |>
req_perform()

body <- resp |> resp_body_json()

df <- purrr::map_dfr(names(body$data), function(geo_key) {
geo <- body$data[[geo_key]]
tibble(
state = geo$name,
population = geo$variables$pop_total$estimate
)
}) |>
mutate(state = forcats::fct_reorder(state, population, .desc = TRUE))

ggplot(df, aes(x = state, y = population, fill = state)) +
geom_col(show.legend = FALSE) +
scale_y_continuous(labels = label_comma()) +
scale_fill_brewer(palette = "Blues", direction = -1) +
labs(
title = "Population by State — 2024 ACS 5-Year Estimates",
subtitle = "Source: PrairieCloud API",
x = NULL,
y = "Population"
) +
theme_minimal()

Next Steps