Ask an online service for information and check its reply
source data loading
API
JSON
Learn how an API request becomes a checked data table in R.
A librarian gathering possible books about natural language processing could copy search results from a catalog into a table. Search results are candidates, not recommendations. The librarian still needs to judge whether each record is relevant, accurate, and useful.
An application programming interface, usually called an API, gives software a defined way to ask another service for information. This lesson reconstructs a query sent to the Open Library Search API and checks the saved reply. The displayed table comes from a dated response so the published output can be reproduced even when the service is unavailable or its ranking changes.
TipWhat you will learn
By the end of this lesson, you will be able to:
explain the request-and-response pattern used by an API;
add search choices to an API request;
check whether the request succeeded;
turn a JSON response into a table in R;
replay a rate limit, a second page, and a changed schema from saved responses; and
handle changing results, service limits, and secret API keys.
Start with a request and a reply
Every API exchange in this lesson has two parts:
R sends a request to a web address.
The service sends a response back.
The request records the librarian’s question. The response contains a status code, descriptive details called headers, and a body holding the requested data.
Our request uses this endpoint, the web address assigned to book searches: https://openlibrary.org/search.json.
Build the request
We build the request before sending it so each choice remains visible. The httr2 package adds those choices one step at a time.
The printed request begins with GET, the standard method used here to ask a service to send information. Printing it lets us inspect the destination and search choices without contacting the service again.
The choices added to the address are called query parameters:
Query parameters used in the Open Library request
Parameter
What it asks for
q
Books related to the words “natural language processing.”
fields
Only the four pieces of information used in this lesson.
limit
No more than five results.
The user-agent identifies the project making the request. The 20-second timeout keeps R from waiting indefinitely. The retry setting permits up to three attempts when a temporary connection problem interrupts the exchange; it does not guarantee that an unavailable service will respond.
Understand what happened when the request was sent
The original request was sent once on the retrieval date recorded below. During this lesson, req_perform() receives the same request but uses a local mock that replays the saved response bytes. That exercises the request-to-response step without contacting Open Library again. A normal req_perform() call would stop on an unsuccessful HTTP status such as 404 or 500.
JSON, short for JavaScript Object Notation, is a text format that can hold named values, lists, and records. Many programming languages can read it, which makes it common in API responses.
Keep a dated response
One successful request cannot guarantee that the service will be available tomorrow or return the same ranking. The repository keeps the JSON response with its retrieval time, request address, HTTP status, content type, record level, licensing reference, and fingerprint.
The MD5 fingerprint detects an accidental change to the saved file. It is not proof that the source is accurate or trustworthy. The request address and API documentation remain in the metadata file for a later audit.
Perform the request against the saved reply
A mock replaces the network exchange with a local function. httr2 still passes the prepared request into req_perform() and returns an httr2_response; the mock supplies the dated status, header, and JSON body.
The prepared request returns a replayed httr2 response
Response detail
Value
Response class
httr2_response
HTTP status
200
Content type
application/json
Turn the saved response into a table
Open Library places search results inside a JSON section named docs. resp_body_json() translates that section into rows and columns because this response has the same fields for each catalog record.
Open Library returns work-level catalog records by default. A row can represent many editions of the same work. The table contains candidate search results, not a checked reading list or a judgment about a book’s quality.
The authors and titles are escaped before display so API text cannot become page markup. Only the validated Open Library addresses are rendered as links.
Live data can change
Open Library may add records, correct dates, or reorder results. Nothing in this page contacts the service while it builds, so the displayed table comes from the saved response and stays stable.
When the request is sent again, the checks worth applying describe a bounded structure rather than five specific titles:
the service returned a successful status;
the response is JSON;
the response contains between one and five records; and
every returned record has a title.
The saved fixture makes the displayed table repeatable. Its metadata records when and how it was retrieved. Open Library states that the Internet Archive does not assert new proprietary rights over the database, while warning that existing rights may vary by contribution and jurisdiction. Reusers should read the current licensing statement rather than assume a blanket license.
Respect the service’s rate limit and stated preference for low-volume, human-facing requests.
Replay the awkward responses
A successful reply is the easy case. The three awkward ones are a refusal, a result set too large for one reply, and a reply whose fields have moved. Each is reconstructed below from the status codes and body shapes the service documents, built inside the lesson with httr2::response(). No request leaves the machine.
Four saved replies and what the reading function did with each
Saved reply
Outcome
Records used
429 with delay-seconds
rate limited
0
429 with HTTP-date
rate limited
0
page 1 of results
more pages
5
page 2 of results
last page
2
title field renamed
schema failure: missing title
0
docs field missing
schema failure: missing top-level docs
0
Six saved replies exercised two retry formats, two result pages, and two schema failures. None produced an empty result table without an error.
The refusal returned no records and a waiting time taken from the Retry-After header, which is the service telling the client how long to pause. Guessing that number, or retrying immediately, is how a temporary limit becomes a ban.
The first page reports start = 0, returns five records, and says seven exist, so start + records returned < numFound supplies the next offset. The second page reports start = 5; adding its two records reaches the reported total, so the client stops. A full page alone would not prove that another page exists.
The renamed field is harder to catch. The record still parsed as JSON and still had a key. Only the explicit list of required fields turned it into a reported failure instead of a table with a missing column.
Do not let the arithmetic pass unexamined
The service reported seven matching records. Seven rows arrived across two pages. Six of them were distinct, because one work appeared on both pages, which happens when ranking shifts between two requests.
pagination_report <-tibble(measure =c("reported by the service","rows received","distinct works kept","unexplained shortfall" ),value =c( first_page$reported_total,nrow(collected),nrow(distinct_records), shortfall ))knitr::kable( pagination_report,col.names =c("Measure", "Count"),caption ="Reported, received, and kept records after paging",row.names =FALSE)
Reported, received, and kept records after paging
Measure
Count
reported by the service
7
rows received
7
distinct works kept
6
unexplained shortfall
1
The shortfall of one is the useful number. A collection script that reported six records without it would look complete. Record the totals the service claimed, the rows you received, and the rows you kept, and treat any gap as a question about the collection rather than a rounding detail.
Keep API keys private
Open Library’s search endpoint does not require a key. Many other APIs do. An API key is a secret value that identifies an account and may permit billable or private access.
Never place a real key in:
an R or Quarto file;
a public repository;
a screenshot; or
printed output.
Store secrets outside the project, usually in an environment variable, a named setting that R can read at run time. If a key is exposed, revoke it and create a replacement.
When an API request fails
A failed request leaves the librarian without a checked list. Turning that failure into an empty table would hide the difference between “no books found” and “the service did not answer.” Report the failure and keep the reported error. Then ask:
Is the device connected to the internet?
Is the endpoint address correct?
Does the service require a key or other permission?
Did the request exceed a rate limit?
Did the response format change?
The code in this lesson stops when the request fails or the response lacks the expected structure. The interruption is visible rather than being published as an apparently complete result.
What to remember
An API request asks a service for structured information.
Query parameters describe what to return and how much.
Check the status and response type before using the body.
Some JSON structures can be reshaped into rows and columns in R.
A refusal, a second page, and a renamed field can all be rehearsed from saved replies before any request is sent.
Honor the waiting time a service sends instead of guessing one.
Compare the total a service reports with the rows you actually kept.
Live API results may change even when the code stays the same.
A dated fixture can preserve the output used in a published example.
Keys and other secrets never belong in published code.
The librarian ends with five candidate records, not five recommendations. The request can be tried again, while the dated fixture preserves the reply shown here. Human judgment still decides which books belong on the reading list.