Making HTTP Requests with httr2
Send GET and POST requests, handle headers, and process responses.
Making HTTP Requests with httr2 is a free R Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Introduction to httr2
httr2 is the modern R package for HTTP requests, succeeding httr. It uses a pipe-based builder pattern: start with request(url), add modifiers, then execute with req_perform().
library(httr2)
# Basic GET request pattern:
# request(url) -> create request object
# |> req_*() -> modify request
# |> req_perform() -> send request
# |> resp_*() -> extract from response
# Minimal example (requires internet):
# resp <- request('https://httpbin.org/get') |>
# req_perform()
# resp_status(resp) # 200
# resp_body_json(resp) # parsed JSON body
cat('httr2 follows: build -> perform -> extract')request() and req_perform()
request(url) creates a request object. req_perform() executes it and returns a response object. The response can then be inspected with resp_* functions.
library(httr2)
# Build and send a GET request
# resp <- request('https://httpbin.org/get') |>
# req_perform()
# Inspect response
# resp_status(resp) # 200
# resp_status_desc(resp) # 'OK'
# resp_headers(resp) # list of headers
# resp_header(resp, 'content-type') # single header
# resp_body_string(resp) # raw body as string
# resp_body_json(resp) # parsed JSON
# resp_body_raw(resp) # raw bytes
cat('Response hierarchy:')
cat('status -> headers -> body')req_headers(): Custom Headers
req_headers() adds or overrides HTTP headers. Used for authentication tokens, content type specification, API version headers, and custom request metadata.
library(httr2)
# Add custom headers
# resp <- request('https://api.example.com/data') |>
# req_headers(
# 'Authorization' = 'Bearer my_token',
# 'X-API-Version' = '2',
# 'Accept' = 'application/json'
# ) |>
# req_perform()
# Common headers:
# 'Content-Type' = 'application/json' for POST with JSON body
# 'Accept' = 'application/json' to request JSON response
# 'User-Agent' = 'MyApp/1.0' for polite identification
# 'X-API-Key' = key for key-based auth
cat('req_headers() sets HTTP request headers')req_url_query(): Query Parameters
req_url_query() appends query parameters to the URL safely (encoding special characters). Cleaner than manually concatenating strings with paste0().
library(httr2)
# Add query parameters
# resp <- request('https://api.example.com/search') |>
# req_url_query(
# q = 'R programming',
# page = 1,
# size = 20,
# sort = 'relevance'
# ) |>
# req_perform()
# Resulting URL:
# https://api.example.com/search?q=R+programming&page=1&size=20&sort=relevance
# Inspect the URL without performing:
req <- request('https://api.example.com/search') |>
req_url_query(q = 'hello world', page = 2)
req$url
# 'https://api.example.com/search?q=hello+world&page=2'POST Requests with req_body_json()
Send JSON data in a POST request with req_body_json(). It automatically sets the Content-Type: application/json header and serializes the R list to JSON.
library(httr2)
# POST request with JSON body
# resp <- request('https://api.example.com/users') |>
# req_method('POST') |>
# req_body_json(list(
# name = 'Alice',
# email = 'alice@example.com',
# role = 'admin'
# )) |>
# req_perform()
# resp_status(resp) # 201 Created (if success)
# resp_body_json(resp) # returned user object
# Other body methods:
# req_body_form(...) -> application/x-www-form-urlencoded
# req_body_raw(bytes) -> raw bytes
# req_body_file(path) -> file upload
cat('req_body_json() handles Content-Type automatically')resp_body_json(): Parsing Response
resp_body_json() parses the response body as JSON into an R list. Use simplifyVector=TRUE (default) to auto-convert JSON arrays to R vectors and objects to named lists.
library(httr2)
library(jsonlite)
# Simulated API response handling
# resp <- request('https://api.github.com/users/hadley') |>
# req_perform()
# user <- resp_body_json(resp)
# user$name # 'Hadley Wickham'
# user$public_repos # number of repos
# user$followers # follower count
# For arrays (simplifyVector=TRUE converts to data frame):
# resp <- request('https://api.github.com/users/hadley/repos') |>
# req_perform()
# repos <- resp_body_json(resp, simplifyVector = TRUE)
# repos$name # vector of repo names
cat('resp_body_json() with simplifyVector=TRUE -> data frame')resp_status() and Error Handling
resp_check_status() throws an error for 4xx/5xx responses automatically. Without it, httr2 doesn't error on bad status codes — you must check explicitly or call resp_check_status().
library(httr2)
# Pattern: check status after perform
# resp <- request('https://api.example.com/data') |>
# req_perform() |>
# resp_check_status() # errors on 4xx/5xx
# Manual status checks:
# status <- resp_status(resp)
# if (status == 200) { ... }
# if (status == 404) { stop('Not found') }
# if (status == 401) { stop('Unauthorized') }
# if (status == 429) { Sys.sleep(60); retry() }
# HTTP status codes:
# 200 OK, 201 Created, 204 No Content
# 400 Bad Request, 401 Unauthorized, 403 Forbidden
# 404 Not Found, 429 Rate Limited
# 500 Server Error, 503 Service Unavailable
cat('Always check response status codes')req_retry(): Automatic Retries
req_retry() automatically retries failed requests. Specify max_tries and optionally is_transient (a function identifying retryable errors like 429 or 503). Essential for robust API clients.
library(httr2)
# Automatic retry with exponential backoff
# resp <- request('https://api.example.com/data') |>
# req_retry(
# max_tries = 3,
# is_transient = function(resp) {
# resp_status(resp) %in% c(429, 500, 503)
# },
# backoff = ~ 2^.x # exponential: 2, 4, 8 seconds
# ) |>
# req_perform()
# Default retry behavior:
# - Retries on 429 Too Many Requests automatically
# - Uses Retry-After header if present
# - max_tries = 1 by default (no retry)
# Simple retry:
# req_retry(max_tries = 3) # retry up to 3 times total
cat('req_retry() adds resilience to API calls')req_throttle(): Rate Limiting
req_throttle(rate) ensures you don't exceed a maximum request rate. Pass rate = n/period (e.g., 10 requests per minute). httr2 automatically sleeps between requests as needed.
library(httr2)
# Throttle to at most 10 requests per minute
# urls <- paste0('https://api.example.com/items/', 1:50)
# resps <- lapply(urls, function(url) {
# request(url) |>
# req_throttle(rate = 10 / 60) |> # 10/min
# req_perform()
# })
# Alternative: use req_perform_parallel() for parallel
# with throttle built in:
# reqs <- lapply(urls, \(u) request(u))
# resps <- req_perform_parallel(
# reqs,
# on_error = 'continue', # skip failures
# progress = TRUE
# )
cat('req_throttle(rate = 10/60) = 10 req/min')Authentication Helpers
httr2 provides built-in authentication helpers: req_auth_basic(user, pass) for Basic auth, req_auth_bearer_token(token) for Bearer tokens, and req_oauth_*() for OAuth flows.
library(httr2)
# Bearer token (most common for modern APIs)
# resp <- request('https://api.example.com/data') |>
# req_auth_bearer_token('my_api_token_here') |>
# req_perform()
# Basic authentication
# resp <- request('https://api.example.com/data') |>
# req_auth_basic('username', 'password') |>
# req_perform()
# Store tokens securely in environment variables
# token <- Sys.getenv('MY_API_TOKEN')
# resp <- request('https://api.example.com') |>
# req_auth_bearer_token(token) |>
# req_perform()
cat('Never hardcode tokens in scripts!')
cat('Use Sys.getenv() or the keyring package')Dry Run with req_dry_run()
req_dry_run() shows exactly what request would be sent (method, URL, headers, body) without actually sending it. Essential for debugging complex requests before hitting a real API.
library(httr2)
# Inspect the request without sending it
req <- request('https://api.example.com/users') |>
req_method('POST') |>
req_headers(
'X-API-Version' = '2',
'Accept' = 'application/json'
) |>
req_auth_bearer_token('my_token') |>
req_body_json(list(name = 'Alice', role = 'admin')) |>
req_url_query(notify = 'true')
# Show request details without sending
req_dry_run(req)
# POST /users?notify=true HTTP/1.1
# Host: api.example.com
# Authorization: Bearer my_token
# Content-Type: application/json
# ...Quick Check
Test your understanding of httr2's request building pattern.
Recap: HTTP Requests with httr2
Key takeaways: httr2 uses a pipe-based builder: request(url) |> req_*() |> req_perform(). Add headers with req_headers(), query params with req_url_query(), JSON body with req_body_json(). Authenticate with req_auth_bearer_token(). Always check status with resp_check_status(). Add resilience with req_retry() and throttle with req_throttle(). Debug with req_dry_run().
library(httr2)
# Complete httr2 request pattern:
# resp <- request('https://api.example.com/endpoint') |>
# req_headers('Accept' = 'application/json') |>
# req_url_query(param1 = 'value', page = 1) |>
# req_auth_bearer_token(Sys.getenv('API_TOKEN')) |>
# req_retry(max_tries = 3) |>
# req_throttle(rate = 10/60) |>
# req_perform() |>
# resp_check_status()
# Extract data:
# data <- resp_body_json(resp, simplifyVector = TRUE)
cat('build -> authenticate -> perform -> check -> extract')Frequently asked questions
Is the “Making HTTP Requests with httr2” lesson free?
Yes — the full text of “Making HTTP Requests with httr2” is free to read here on the web, and the R Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the R Academy course, upgrade to CoddyKit PRO.
What will I learn in “Making HTTP Requests with httr2”?
Send GET and POST requests, handle headers, and process responses. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start R Academy?
No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Making HTTP Requests with httr2” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this R Academy lesson?
Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Parsing JSON with jsonlite
- Making HTTP Requests with httr2
- Consuming REST APIs in R
- Handling Nested JSON Structures