Retrieving data from APIs with httr2

Introduction to JSON files and interacting with APIs with httr2
Author
Affiliation

Department of Biostatistics, Johns Hopkins

Published

November 7, 2024

Before we begin, let’s load a few R packages

library(tidyverse)
library(jsonlite)
library(httr2)

Motivation

Today, we are going to talk about getting data from APIs and examples of common data formats.

First, let’s have a bit of a philosophical discussion about data.

“Raw” vs “Clean” data

As data analysts, this is what we wished data looked like whenever we start a project

However, the reality, is data is rarely in that form in comes in all types of “raw” formats that need to be transformed into a “clean” format.

For example, in field of genomics, raw data looks like something like this:

Or if you are interested in analyzing data from Twitter:

Or data from Electronic Healthcare Records (EHRs):

We all have our scary spreadsheet tales. Here is Jenny Bryan from Posit and UBC actually asking for some of those spreadsheet tales on twitter.

For example, this is an actual spreadsheet from Enron in 2001:

What do we mean by “raw” data?

From https://simplystatistics.org/posts/2016-07-20-relativity-raw-data/ raw data is defined as data…

…if you have done no processing, manipulation, coding, or analysis of the data. In other words, the file you received from the person before you is untouched. But it may not be the rawest version of the data. The person who gave you the raw data may have done some computations. They have a different “raw data set”.

Where do data live?

Data lives anywhere and everywhere. Data might be stored simply in a .csv or .txt file. Data might be stored in an Excel or Google Spreadsheet. Data might be stored in large databases that require users to write special functions to interact with to extract the data they are interested in.

For example, you may have heard of the terms mySQL or MongoDB.

From Wikipedia, MySQL is defined as an open-source relational database management system (RDBMS). Its name is a combination of “My”, the name of co-founder Michael Widenius’s daughter,[7] and “SQL”, the abbreviation for Structured Query Language.

From Wikipeda, MongoDB is defined as “a free and open-source cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schemata.”

So after reading that, we get the sense that there are multiple ways large databases can be structured, data can be formatted and interacted with. In addition, we see that database programs (e.g. MySQL and MongoDB) can also interact with each other.

We will learn more about JSON today and learn about SQL in a later lecture more formally.

Best practices on sharing data

A great article in PeerJ was written titled How to share data for collaboration, in which the authors describe a set of guidelines for sharing data:

We highlight the need to provide raw data to the statistician, the importance of consistent formatting, and the necessity of including all essential experimental information and pre-processing steps carried out to the statistician. With these guidelines we hope to avoid errors and delays in data analysis. the importance of consistent formatting, and the necessity of including all essential experimental information and pre-processing steps carried out to the statistician.

It’s a great paper that describes the information you should pass to a statistician to facilitate the most efficient and timely analysis.

Specifically:

  1. The raw data (or the rawest form of the data to which you have access)
    • Should not have modified, removed or summarized any data; Ran no software on data
    • e.g. strange binary file your measurement machine spits out
    • e.g. complicated JSON file you scrapped from Twitter Application Programming Interfaces (API)
    • e.g. hand-entered numbers you collected looking through a microscope
  2. A clean data set
    • This may or may not be transforming data into a tidy dataset, but possibly yes
  3. A code book describing each variable and its values in the clean or tidy data set.
    • More detailed information about the measurements in the data set (e.g. units, experimental design, summary choices made)
    • Doesn’t quite fit into the column names in the spreadsheet
    • Often reported in a .md, .txt or Word file.

  1. An explicit and exact recipe you used to go from 1 -> 2,3

Getting data

JSON files

JSON (or JavaScript Object Notation) is a file format that stores information in human-readable, organized, logical, easy-to-access manner.

For example, here is what a JSON file looks like:

var stephanie = {
    "job-title" : "Associate Professor",
    "hometown" : "Baltimore, MD",
    "pronouns": "she/her",
  "states-lived" : {
    "state1" : "Louisiana",
    "state2" : "Texas",
    "state3" : "Massachusetts",
    "state4" : "Maryland"
  }
}

Some features about JSON objects:

  • JSON objects are surrounded by curly braces {}
  • JSON objects are written in key/value pairs
  • Keys must be strings, and values must be a valid JSON data type (string, number, object, array, boolean)
  • Keys and values are separated by a colon
  • Each key/value pair is separated by a comma

Overview of APIs

From AWS, API stands for Application Programming Interface.

  • “Application” = any software with a distinct function
  • “Interface” = a contract of service between two applications. This contract defines how the two communicate with each other using requests and responses.

The API documentation contains information on how developers are to structure those requests and responses.

Purpose of APIs

The purpose of APIs is enable two software components to communicate with each other using a set of definitions and protocols.

For example, the weather bureau’s software system contains daily weather data. The weather app on your phone “talks” to this system via APIs and shows you daily weather updates on your phone.

How do APIs work?

To understand how APIs work, two terms that are important are

  1. client. This is the application sending the request.
  2. server. This is the application sending the response.

So in the weather example, the bureau’s weather database is the server, and the mobile app is the client.

Four types of API architectures

There are four different ways that APIs can work depending on when and why they were created.

  1. SOAP APIs. These APIs use Simple Object Access Protocol. Client and server exchange messages using XML. This is a less flexible API that was more popular in the past.

  2. RPC APIs. These APIs are called Remote Procedure Calls. The client completes a function (or procedure) on the server, and the server sends the output back to the client.

  3. Websocket APIs. Websocket API is another modern web API development that uses JSON objects to pass data. A WebSocket API supports two-way communication between client apps and the server. The server can send callback messages to connected clients, making it more efficient than REST API.

  4. REST APIs. REST stands for Representational State Transfer (and are the most popular and flexible APIs). The client sends requests to the server as data. The server uses this client input to start internal functions and returns output data back to the client. REST defines a set of functions like GET, PUT, DELETE, etc. that clients can use to access server data. Clients and servers exchange data using HTTP.

The main feature of REST API is statelessness (i.e. servers do not save client data between requests). Client requests to the server are similar to URLs you type in your browser to visit a website. The response from the server is plain data, without the typical graphical rendering of a web page.

How to use an API?

The basic steps to using an API are:

  1. Obtaining an API key. This is done by creating a verified account with the API provider.
  2. Set up an HTTP API client. This tool allows you to structure API requests easily using the API keys received. Here, we will use functions from the httr2 package (which is the next generation of the httr package).
  3. If you don’t have an API client, you can try to structure the request yourself in your browser by referring to the API documentation.
  4. Once you are comfortable with the new API syntax, you can start using it in your code.

Where can I find new APIs?

New web APIs can be found on API marketplaces and API directories, such as:

  • Rapid API – One of the largest global API markets (10k+ public APIs). Users to test APIs directly on the platform before committing to purchase.
  • Public REST APIs – Groups REST APIs into categories, making it easier to browse and find the right one to meet your needs.
  • APIForThat and APIList – Both these websites have lists of 500+ web APIs, along with in-depth information on how to use them.

GitHub API

The GitHub REST API may be of interest when studying online communities, working methods, organizational structures, communication and discussions, etc. with a focus on (open-source) software development.

Many projects that are hosted on GitHub are open-source projects with a transparent development process and communications. For private projects, which can also be hosted on GitHub, there’s understandably only a few aggregate data available.

Let’s say we want to use the GitHub REST API to find out how many of my GitHub repositories have open issues?

Pro-tip

The API can be used for free and you can send up to 60 requests per hour if you are not authenticated (i.e. if you don’t provide an API key).

For serious data collection, this is not much, so it is recommended to sign up on GitHub and generate a personal access token that acts as API key.

This token can then be used to authenticate your API requests. Your quota is then 5000 requests per hour.

Access the API from R

There are packages for many programming languages that provide convenient access for communicating with the GitHub API, but there are no such packages (that I’m aware of) for accessing the API from R.

This means we can only access the API directly, e.g. by using the jsonlite package to fetch the data and convert it to an R list or data.frame.

Specifically, we will use the jsonlite::read_json() function to read a JSON file into a data frame.

The JSON file is located at https://api.github.com/users/stephaniehicks/repos.

github_url <- "https://api.github.com/users/stephaniehicks/repos"

library(jsonlite)
library(tidyverse)
jsonData <- read_json(github_url, simplifyVector = TRUE)
glimpse(jsonData)
Rows: 30
Columns: 79
$ id                          <int> 160194123, 132884754, 647539937, 225501707…
$ node_id                     <chr> "MDEwOlJlcG9zaXRvcnkxNjAxOTQxMjM=", "MDEwO…
$ name                        <chr> "2018-bioinfosummer-scrnaseq", "advdatasci…
$ full_name                   <chr> "stephaniehicks/2018-bioinfosummer-scrnase…
$ private                     <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
$ owner                       <df[,19]> <data.frame[26 x 19]>
$ html_url                    <chr> "https://github.com/stephaniehicks/201…
$ description                 <chr> NA, NA, "Repo to share code for the atlas-…
$ fork                        <lgl> FALSE, TRUE, FALSE, TRUE, TRUE, FALSE, FAL…
$ url                         <chr> "https://api.github.com/repos/stephaniehic…
$ forks_url                   <chr> "https://api.github.com/repos/stephaniehic…
$ keys_url                    <chr> "https://api.github.com/repos/stephaniehic…
$ collaborators_url           <chr> "https://api.github.com/repos/stephaniehic…
$ teams_url                   <chr> "https://api.github.com/repos/stephaniehic…
$ hooks_url                   <chr> "https://api.github.com/repos/stephaniehic…
$ issue_events_url            <chr> "https://api.github.com/repos/stephaniehic…
$ events_url                  <chr> "https://api.github.com/repos/stephaniehic…
$ assignees_url               <chr> "https://api.github.com/repos/stephaniehic…
$ branches_url                <chr> "https://api.github.com/repos/stephaniehic…
$ tags_url                    <chr> "https://api.github.com/repos/stephaniehic…
$ blobs_url                   <chr> "https://api.github.com/repos/stephaniehic…
$ git_tags_url                <chr> "https://api.github.com/repos/stephaniehic…
$ git_refs_url                <chr> "https://api.github.com/repos/stephaniehic…
$ trees_url                   <chr> "https://api.github.com/repos/stephaniehic…
$ statuses_url                <chr> "https://api.github.com/repos/stephaniehic…
$ languages_url               <chr> "https://api.github.com/repos/stephaniehic…
$ stargazers_url              <chr> "https://api.github.com/repos/stephaniehic…
$ contributors_url            <chr> "https://api.github.com/repos/stephaniehic…
$ subscribers_url             <chr> "https://api.github.com/repos/stephaniehic…
$ subscription_url            <chr> "https://api.github.com/repos/stephaniehic…
$ commits_url                 <chr> "https://api.github.com/repos/stephaniehic…
$ git_commits_url             <chr> "https://api.github.com/repos/stephaniehic…
$ comments_url                <chr> "https://api.github.com/repos/stephaniehic…
$ issue_comment_url           <chr> "https://api.github.com/repos/stephaniehic…
$ contents_url                <chr> "https://api.github.com/repos/stephaniehic…
$ compare_url                 <chr> "https://api.github.com/repos/stephaniehic…
$ merges_url                  <chr> "https://api.github.com/repos/stephaniehic…
$ archive_url                 <chr> "https://api.github.com/repos/stephaniehic…
$ downloads_url               <chr> "https://api.github.com/repos/stephaniehic…
$ issues_url                  <chr> "https://api.github.com/repos/stephaniehic…
$ pulls_url                   <chr> "https://api.github.com/repos/stephaniehic…
$ milestones_url              <chr> "https://api.github.com/repos/stephaniehic…
$ notifications_url           <chr> "https://api.github.com/repos/stephaniehic…
$ labels_url                  <chr> "https://api.github.com/repos/stephaniehic…
$ releases_url                <chr> "https://api.github.com/repos/stephaniehic…
$ deployments_url             <chr> "https://api.github.com/repos/stephaniehic…
$ created_at                  <chr> "2018-12-03T13:20:45Z", "2018-05-10T10:22:…
$ updated_at                  <chr> "2019-08-08T02:18:17Z", "2018-05-10T10:22:…
$ pushed_at                   <chr> "2018-12-05T17:07:09Z", "2017-12-18T17:18:…
$ git_url                     <chr> "git://github.com/stephaniehicks/2018-bioi…
$ ssh_url                     <chr> "git@github.com:stephaniehicks/2018-bioinf…
$ clone_url                   <chr> "https://github.com/stephaniehicks/2018-bi…
$ svn_url                     <chr> "https://github.com/stephaniehicks/2018-bi…
$ homepage                    <chr> NA, NA, NA, NA, NA, "", NA, NA, NA, NA, NA…
$ size                        <int> 60296, 172353, 8866, 121, 675, 26688, 20, …
$ stargazers_count            <int> 4, 0, 2, 1, 0, 0, 1, 8, 0, 1, 0, 15, 3, 0,…
$ watchers_count              <int> 4, 0, 2, 1, 0, 0, 1, 8, 0, 1, 0, 15, 3, 0,…
$ language                    <chr> "TeX", "HTML", "R", NA, NA, "R", "R", "Jup…
$ has_issues                  <lgl> TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, TRU…
$ has_projects                <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, …
$ has_downloads               <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, …
$ has_wiki                    <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, …
$ has_pages                   <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, F…
$ has_discussions             <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
$ forks_count                 <int> 4, 0, 1, 0, 1, 0, 0, 2, 0, 0, 0, 4, 1, 1, …
$ mirror_url                  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ archived                    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
$ disabled                    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
$ open_issues_count           <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ license                     <df[,5]> <data.frame[26 x 5]>
$ allow_forking               <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, …
$ is_template                 <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS…
$ web_commit_signoff_required <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
$ topics                      <list> <>, <>, <>, <>, <>, <>, <>, <>, <>, <>, <>…
$ visibility                  <chr> "public", "public", "public", "public", "p…
$ forks                       <int> 4, 0, 1, 0, 1, 0, 0, 2, 0, 0, 0, 4, 1, 1,…
$ open_issues                 <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ watchers                    <int> 4, 0, 2, 1, 0, 0, 1, 8, 0, 1, 0, 15, 3, 0,…
$ default_branch              <chr> "master", "master", "main", "master", "mas…

We see this has returned a data frame with the argument simplifyVector which converts the output from a list to a dataframe.

However, from here, we see that there are only 30 rows (or 30 repositories). If you look on my github page, you can see there are more than 30 repositories.

APIs limit info from users

What’s happening is called pagination.

At a high-level, the API is limiting the amount of items a user gets and splitting it into pages.

Formally, pagination is the process of splitting the contents or a section of a website into discrete pages. Users tend to get lost when there’s bunch of data and with pagination splitting they can concentrate on a particular amount of content. Hierarchy and paginated structure improve the readability score of the content.

In this use case Github api splits the result into 30 items per resonse, depends on the request

Solution: You should explicitly specify in your request how many items you would like to receive from server pagination engine, using formula for Github pagination api:

?page=1&per_page=<numberOfItemsYouSpecify>"

You can read more about pagination here:

Example

Here we can visit this website:

And see there are more than 30 repos. Let’s read it into R.

github_url = "https://api.github.com/users/stephaniehicks/repos?page=1&per_page=1000"

jsonDataAll <- read_json(github_url, simplifyVector = TRUE)
dim(jsonDataAll)
[1] 92 79

We now get all the public repositories! yay!

Access APIs with httr2

There are a set of basic HTTP verbs that allow you access a set of endpoints.

The basic request patterns are:

  • Retrieve a single item (GET)
  • Retrieve a list of items (GET)
  • Create an item (POST)
  • Update an item (PUT)
  • Delete an item (DELETE)

Example: GitHub commits

Let’s say you want to retrieve information about the latest commits from a GitHub repository. We will use httr2 to make a request to the GitHub API for a repository of your choice. Later on we will make this an authenticated HTTP response to the GitHub API.

First, we make sure we have the httr2 package installed and loaded. We’ll also need jsonlite package for handling JSON files and dplyr for data wrangling.

library(httr2)
library(jsonlite)
library(dplyr)

Now we will set up our request to the GitHub API. The GitHub API endpoint for getting the latest commits in a repository is at https://api.github.com/repos/{owner}/{repo}/commits.

For this example, we’ll look at the latest commits for the tidyverse/dplyr repository. We will use httr2::request() function to set up the request, (and later on we will add authentication – optional, but recommended for higher rate limits), and parse the response.

owner <- "tidyverse"
repo <- "dplyr"
url <- paste0("https://api.github.com/repos/", owner, "/", repo, "/commits")


response <- request(url) %>%
  req_perform()
response
<httr2_response>
GET https://api.github.com/repos/tidyverse/dplyr/commits
Status: 200 OK
Content-Type: application/json
Body: In memory (146056 bytes)

Next, we can see if the response was successful.

# Check if the response was successful
if (resp_status(response) == 200) {
  # Parse JSON response into an R list
  commits <- resp_body_json(response, simplifyVector = TRUE)
  
  # View the first few rows of the commits data
  head(commits)
} else {
  message("Failed to retrieve data. Status code: ", resp_status(response))
}
                                       sha
1 fb25640fa1eb74746a7a74a06090045106e5d20f
2 e4e9a295a373b85e02ae084a23f12e9212a72b98
3 1d17672a54305170dc75c251f8ae69a85c0bea37
4 cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84
5 85e94fcde02ad49c77510991078899278489fd8f
6 e0555277878ed174d40bda86690674ecdc27db55
                                                                node_id
1 C_kwDOAGIUpdoAKGZiMjU2NDBmYTFlYjc0NzQ2YTdhNzRhMDYwOTAwNDUxMDZlNWQyMGY
2 C_kwDOAGIUpdoAKGU0ZTlhMjk1YTM3M2I4NWUwMmFlMDg0YTIzZjEyZTkyMTJhNzJiOTg
3 C_kwDOAGIUpdoAKDFkMTc2NzJhNTQzMDUxNzBkYzc1YzI1MWY4YWU2OWE4NWMwYmVhMzc
4 C_kwDOAGIUpdoAKGNmYjI1YTAzMGY5ZjdjMzlmNzdmZWQyYzk3ZjNmYTdiMTVhNTVlODQ
5 C_kwDOAGIUpdoAKDg1ZTk0ZmNkZTAyYWQ0OWM3NzUxMDk5MTA3ODg5OTI3ODQ4OWZkOGY
6 C_kwDOAGIUpdoAKGUwNTU1Mjc3ODc4ZWQxNzRkNDBiZGE4NjY5MDY3NGVjZGMyN2RiNTU
    commit.author.name                         commit.author.email
1        Kirill Müller                           kirill@cynkra.com
2        Davis Vaughan                           davis@rstudio.com
3              Mike Du 58779940+ilovemane@users.noreply.github.com
4                 Adam                       adampeterso@gmail.com
5 Núria Mercadé-Besora 61558739+nmercadeb@users.noreply.github.com
6           James Wade                       github@jameshwade.com
    commit.author.date commit.committer.name commit.committer.email
1 2024-11-02T17:43:50Z         Kirill Müller      kirill@cynkra.com
2 2024-10-01T15:53:13Z                GitHub     noreply@github.com
3 2024-08-27T16:31:39Z                GitHub     noreply@github.com
4 2024-08-27T16:30:57Z                GitHub     noreply@github.com
5 2024-08-27T15:51:07Z                GitHub     noreply@github.com
6 2024-08-27T15:40:46Z                GitHub     noreply@github.com
  commit.committer.date
1  2024-11-02T17:44:39Z
2  2024-10-01T15:53:13Z
3  2024-08-27T16:31:39Z
4  2024-08-27T16:30:57Z
5  2024-08-27T15:51:07Z
6  2024-08-27T15:40:46Z
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        commit.message
1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              Move to tidyverse, already applied manually to gh-pages
2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  Remove not needed `new_expanded_quosures()` (#7090)
3                                                                                                                                                                                                                                                                                               Add error message when passing an array in `conditions` (#7069)\n\n* add error message when passing a matrix\r\n\r\ntidyverse day\r\n\r\n* Update test-vec-case-when.R\r\n\r\n* fixes\r\n\r\n* Tweaks\r\n\r\n* NEWS bullet\r\n\r\n* Update snap\r\n\r\n---------\r\n\r\nCo-authored-by: Davis Vaughan <davis@posit.co>
4                                                                                                                                                                                                                                                                   Add documentation clarifying appropriate use of weights in `slice_sample()` (#7052)\n\n* Add documentation clarifying appropriate use of weights in dplyr's `slice_sample()`.\r\n\r\n* Add documentation to relevant .Rd file.\r\n\r\n* Tweak documentation placement a bit\r\n\r\n---------\r\n\r\nCo-authored-by: Davis Vaughan <davis@posit.co>
5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           Document that `group_by()` works with data-masking (#7071)
6 Add a `ptype` argument to `between()` (#7073)\n\n* add ptype argument to between Fixes #6906\r\n\r\n* update NEWS with ptype argument\r\n\r\n* document() and add tests\r\n\r\n* add back see also\r\n\r\n* add back see also\r\n\r\n* Redocument\r\n\r\n* Trim trailing whitespace\r\n\r\n* Remove extra line\r\n\r\n* Minor docs tweaks\r\n\r\n* require names ptype, update tests, update function documentation\r\n\r\n* remove unnecessary if else blocks\r\n\r\n* Tweak NEWS bullet\r\n\r\n* A few more tweaks\r\n\r\n* Few more tweaks\r\n\r\n---------\r\n\r\nCo-authored-by: Davis Vaughan <davis@posit.co>
                           commit.tree.sha
1 5aaa2c4dcdabd6c8303b660f15c963740193eb17
2 8299635297b6b5e61c9522b525a5414993405e08
3 9144e07c3b09886b79b64a76904501f565f1b6c8
4 3d6a95112c5bc59e6a60d245704984e722aeacad
5 852e02c951972c5a4e9a831c68e2425085e7fdd5
6 18d4e6763ceb64f3967f9addb712ad0878798b38
                                                                                  commit.tree.url
1 https://api.github.com/repos/tidyverse/dplyr/git/trees/5aaa2c4dcdabd6c8303b660f15c963740193eb17
2 https://api.github.com/repos/tidyverse/dplyr/git/trees/8299635297b6b5e61c9522b525a5414993405e08
3 https://api.github.com/repos/tidyverse/dplyr/git/trees/9144e07c3b09886b79b64a76904501f565f1b6c8
4 https://api.github.com/repos/tidyverse/dplyr/git/trees/3d6a95112c5bc59e6a60d245704984e722aeacad
5 https://api.github.com/repos/tidyverse/dplyr/git/trees/852e02c951972c5a4e9a831c68e2425085e7fdd5
6 https://api.github.com/repos/tidyverse/dplyr/git/trees/18d4e6763ceb64f3967f9addb712ad0878798b38
                                                                                         commit.url
1 https://api.github.com/repos/tidyverse/dplyr/git/commits/fb25640fa1eb74746a7a74a06090045106e5d20f
2 https://api.github.com/repos/tidyverse/dplyr/git/commits/e4e9a295a373b85e02ae084a23f12e9212a72b98
3 https://api.github.com/repos/tidyverse/dplyr/git/commits/1d17672a54305170dc75c251f8ae69a85c0bea37
4 https://api.github.com/repos/tidyverse/dplyr/git/commits/cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84
5 https://api.github.com/repos/tidyverse/dplyr/git/commits/85e94fcde02ad49c77510991078899278489fd8f
6 https://api.github.com/repos/tidyverse/dplyr/git/commits/e0555277878ed174d40bda86690674ecdc27db55
  commit.comment_count commit.verification.verified commit.verification.reason
1                    0                        FALSE                   unsigned
2                    0                         TRUE                      valid
3                    0                         TRUE                      valid
4                    0                         TRUE                      valid
5                    0                         TRUE                      valid
6                    0                         TRUE                      valid
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      commit.verification.signature
1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              <NA>
2 -----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJm/BrpCRC1aQ7uu5UhlAAApk0QABIAjfuu47L1gaXmicgRMYpN\nTs1JfHO7zGxafb5QYqgk80yH/w+XsqHt+Q2Xd3JLvIYSWBJ4IAuKU+R4vBOF6yhW\nDprWxU3K0bLeOc9ycmH21WUPSBNjw/dhyFkMULBa6ZbZhsfPddb8OhPSw02ze/Rw\n9oQSVIWC5ZGIHwBp4/dvrdmQ7ztwmFnTp8uegpIdLdfpqCereJdV74VYx7TrGYB4\n/+48s84H2X2p/P3h15riv5t+545uhrMZMP9RspV7etRju4mloG5EP2zpLPgxrCIi\nLhmpZdjKgrOc0AlocOWyjcGzRLSgExYV0bTeJnTpxqZRsZzkM5JMEGEnPCBP5OD3\nd+PGv+l7WeOioB8+r5Sz+asoWqEfv2rfrjibqZpT4BSwV+R4+4p0NFtzOZpBzrCz\nd+7mu0PWyLP4H7RQ0vWNZTMP3YYpQoEL5FX+X9qKKuFnob8n6W4Fje/B1ru4cYip\nvI7sX43WLQ9X32x7FleFHsFAZDKSg8x/QXlJ2T1/VZSv0ge4M/wg5YmzhPi17xSl\n7JcizRSKzqZDtDiIZQD2FjnVMNdL1LsjbnbWJGf02KejxFkgsYa2oEos2ekouilL\n+xswdRrF8Fuh8KNCMc3MGO3nQovjMGgMoaFaYetXhYOTUSGAfQJBOOpNrH0vzf6g\nOSuaLjJrCYFv0IIxdtrA\n=kx9G\n-----END PGP SIGNATURE-----\n
3 -----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJmzf9rCRC1aQ7uu5UhlAAA+k4QADkSbzuLgeAvxtczSazlRvuW\nAYPlvaK02ndKbIY8Ucwp/wyTGgorMH2kC3g3r0cy1bwVHvsNp0gt1S3tbxdvXXEA\n8R0+pHSxgDvzCIkZdE5WJpN1VjQVYAmqRzeVt9ieifuBGVWapIn26fDPndXswPWq\nQWDKh6tZn5wsfWiclR4TSKIcqOrva7AzegB0iyF/8kKer7jOuG/R5OQoTGJoXwlR\nzGdI+JxKBnY9Q24SBo5xruKq3LKQIWUiICm48aFQ35JbsT83v+RuiAfMkrn1C0RM\nQdN0820iz5/oShO5iIXvcIBZSZhSjQ/EQkv394YS0N4gElGwEbXQIOon5nZIzK3d\n7jtXerms96sjUCOXyb0fB6YnbNEA3/Tiq7x61Fy6uUSrblewhd5AXNZu9/aJwvYv\nOj1BoMdr/HSIUnHl11+90Y0RIA66WsHNCZLKm3L2DAlYczGmL/zcFNivH/3epmlB\nMirSkqfuK1AYzaH5z8Fpu9UOkEVw18L+gFwn2fTye3rxzoy6Lr8R9eT/VAUtZCQV\ng3LurvIWjHe1NNVxeQ702E1ACBXcUhD4Bfqij1QZeEFP7bsIo+ggcyyEIn56ayEO\nSmGMb76kcBciL44oU8fAuzWK8nVVA54iM4ecJltYhp1o3tOFpJ+fQA4n5hxFYgc4\nU+1DPZvop1yluVlV1vVV\n=AwJu\n-----END PGP SIGNATURE-----\n
4 -----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJmzf9BCRC1aQ7uu5UhlAAA68AQAEtPIx3MrOwvPhjYi4mbBvZJ\n/EeNZqaiHoanezr8L+AR0Fym/Asnf5h35+t4PDYU/JqGdND/es0LB6GxH3YMNUsK\n6PrGbv4CMs3Ary5si5c7ynAlASCnbcMrvbIUM0UWzWd5eV+am0irDFmZc20g5Wt/\ngZPYGNKK0D3RWM2KP5O6a67zm9ipuqNbAkGRat9qehxvTEblnB/v8jHZGy5WpTjw\nZDxrdgdCHWCHM48viFsJlbEm8V17tFJ/ipy6JthZy7Ei1JyWpmuc28y3VqrofIDF\n58OdA6RA7JDvfhWPFKq9RKjgfSXp5hgz+5edSwc9fTS77KL9MDq8ZK+Mhm12F3x+\nzAb8fdajL4YwTjquwUfKJWYEgrc39vmFo/G84+VtYuRRvi2T8BFlXqTsEw3z109Y\nfFzem/3R0PFn/+M68dzh931G52JEkhlcowR/Dy+cy1bgnWlEDOHZQQUtFGsuRJ/T\nxOByNQ3GIiqxR+40VREeByXuCdLCOrD0KiggwNBIbUb9AU4fwvS3LNxDZLMB8gzJ\n+DRTH/uMwwlySvf/gn5NvlasXVnLSTuMCi/DBEUMFpOkfeUG63stOuiiqn0p9ll8\nHuAsLVT72uZOYOMiW+MsskehXZKKnOUlzv4JfoakFIEFMwBSDAdfsq9hWYtdZ9so\ndNwPfMuZLou4JZ4kNJoP\n=2gy1\n-----END PGP SIGNATURE-----\n
5 -----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJmzfXrCRC1aQ7uu5UhlAAAGIkQAAWFfWEtO+rNEL4oWHAm0Fdx\ncKsyfH/j+eIZDIwJ7KF9hcd1VLeVO96lxCtAqW2/x7lYReM/NyfBtz0nQuwaVM01\nstesFeyJZDclN2LgHWPwuq9QM3Gt2M1hrmguDvb6oM5rvT2W0XXKNS5TlPKx3TeU\nZT1RIFfndFuvifQlZjmYMaDI9kLQS8kQtQCTjuv3Ei57uWEAbkj5r2w6SA8HA8Ti\nQxCOWzQ8FTPqyvb3iev3xLmJvXXNr+AJXbJRkIZA3Pwu8CCOmpmf6H6HeBQqMzaG\nkjqX5uqzmyAyf38z7Y+yALQkPySQbNp/de2jea1hrq7v2W78cR4+gIzti09Y1Q9R\nhi5PIXwsykb4XKdZXl3kQfEBxBh7iIbBOglLPHMDRVolQMmj6S9ezjnn0FkpXdKE\nnCPGPI/+6MzTeSPrinfO7wUpXNQocVwBEUV8Qvv8Rc/uHbKw6FLPofZepJ+Z2UmA\npyEvwcADNGXhJM8Ef9O3tdDFhdV5bSORW9bhQh97l9yPneB5+Rui1ftiqKjqR0pC\n9uPNrMoexCc1O/uEv5CotGjViOJrdzbqJxBWO9BD8i8LKNI/Fiuu4OHMQsXwxHye\nqSPrOx4zrAxrg/qn9hEPQFjj+sHwjUV3dHgpSCSqzmLZzPYrAKQS7BMERogzgNke\nT9u5cp6sbK4eI414nNvP\n=4+mC\n-----END PGP SIGNATURE-----\n
6 -----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJmzfN+CRC1aQ7uu5UhlAAAL3cQABd/cNjfNWYYv/iRI12OoHJF\n8Sqp/50IfyuyMgEJT4wGstAUi/Z632TGTQEsEBuvVbrsmlRH6myYEXIUWGEv2RM2\nD4bgykku0rZCeIJFyMjCrnMK1NYOrp3RoqD7HlWFzvpW5ZzNyXZPwafeYvsakh5K\nPt1V4WjYzEioPWTJIgAKEUr8Pr3zePCO0zY7NXAhsgobaxUK0PcDoTt0WeHkxhBL\nwt2f+FylBKSbh3Fw6zv2xINGxEQWwSWa3EZTvsu5s3aTl0OOtz/1vLQ+gCYqhU9Z\n4VHdgj0j2+AbETBPiou8E/szSuuffRTwgSkobbAkgOF6uyFg3gxRM7zjXsDiN4o6\nwxNx72y1/tDanfUCRX/e3zcUh8gqtB4RggHoKtdun7UnAck+kgCeMH+ru5eNINxH\n9K/1xL70FoasI0Kd9k4L0Q2YsoXOhgHVoQFBKC0AgiIWaJrjpx/C+MeMeXj/XgNm\nQfybWEiXHL9S4jq0Wy/3he0BvwwiHcy682/FyeVx0fxUEHw4DzL0LkcBctm+yUKS\n3MFADYcgTiVlafXFdej9mtiElSGINNh/C83Vj+LQdG6axzHeiWX6mGiRCgfEZhpT\n+XhMnpI7XyQoeSyREnuVUx+E2PWsqG0gFocq0KV3UDnc5ZGE3JtH2L9w6CKlW7YA\n8RlhDNCiHgAk2O7S2iE/\n=Z8qV\n-----END PGP SIGNATURE-----\n
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 commit.verification.payload
1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       <NA>
2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               tree 8299635297b6b5e61c9522b525a5414993405e08\nparent 1d17672a54305170dc75c251f8ae69a85c0bea37\nauthor Davis Vaughan <davis@rstudio.com> 1727797993 -0400\ncommitter GitHub <noreply@github.com> 1727797993 -0400\n\nRemove not needed `new_expanded_quosures()` (#7090)\n\n
3                                                                                                                                                                                                                                                                            tree 9144e07c3b09886b79b64a76904501f565f1b6c8\nparent cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84\nauthor Mike Du <58779940+ilovemane@users.noreply.github.com> 1724776299 +0100\ncommitter GitHub <noreply@github.com> 1724776299 -0400\n\nAdd error message when passing an array in `conditions` (#7069)\n\n* add error message when passing a matrix\r\n\r\ntidyverse day\r\n\r\n* Update test-vec-case-when.R\r\n\r\n* fixes\r\n\r\n* Tweaks\r\n\r\n* NEWS bullet\r\n\r\n* Update snap\r\n\r\n---------\r\n\r\nCo-authored-by: Davis Vaughan <davis@posit.co>
4                                                                                                                                                                                                                                                                         tree 3d6a95112c5bc59e6a60d245704984e722aeacad\nparent 85e94fcde02ad49c77510991078899278489fd8f\nauthor Adam <adampeterso@gmail.com> 1724776257 -0400\ncommitter GitHub <noreply@github.com> 1724776257 -0400\n\nAdd documentation clarifying appropriate use of weights in `slice_sample()` (#7052)\n\n* Add documentation clarifying appropriate use of weights in dplyr's `slice_sample()`.\r\n\r\n* Add documentation to relevant .Rd file.\r\n\r\n* Tweak documentation placement a bit\r\n\r\n---------\r\n\r\nCo-authored-by: Davis Vaughan <davis@posit.co>
5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       tree 852e02c951972c5a4e9a831c68e2425085e7fdd5\nparent e0555277878ed174d40bda86690674ecdc27db55\nauthor Núria Mercadé-Besora <61558739+nmercadeb@users.noreply.github.com> 1724773867 +0100\ncommitter GitHub <noreply@github.com> 1724773867 -0400\n\nDocument that `group_by()` works with data-masking (#7071)\n\n
6 tree 18d4e6763ceb64f3967f9addb712ad0878798b38\nparent 173b4232bff810f563e1739211ea7545ed0651e6\nauthor James Wade <github@jameshwade.com> 1724773246 -0400\ncommitter GitHub <noreply@github.com> 1724773246 -0400\n\nAdd a `ptype` argument to `between()` (#7073)\n\n* add ptype argument to between Fixes #6906\r\n\r\n* update NEWS with ptype argument\r\n\r\n* document() and add tests\r\n\r\n* add back see also\r\n\r\n* add back see also\r\n\r\n* Redocument\r\n\r\n* Trim trailing whitespace\r\n\r\n* Remove extra line\r\n\r\n* Minor docs tweaks\r\n\r\n* require names ptype, update tests, update function documentation\r\n\r\n* remove unnecessary if else blocks\r\n\r\n* Tweak NEWS bullet\r\n\r\n* A few more tweaks\r\n\r\n* Few more tweaks\r\n\r\n---------\r\n\r\nCo-authored-by: Davis Vaughan <davis@posit.co>
                                                                                            url
1 https://api.github.com/repos/tidyverse/dplyr/commits/fb25640fa1eb74746a7a74a06090045106e5d20f
2 https://api.github.com/repos/tidyverse/dplyr/commits/e4e9a295a373b85e02ae084a23f12e9212a72b98
3 https://api.github.com/repos/tidyverse/dplyr/commits/1d17672a54305170dc75c251f8ae69a85c0bea37
4 https://api.github.com/repos/tidyverse/dplyr/commits/cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84
5 https://api.github.com/repos/tidyverse/dplyr/commits/85e94fcde02ad49c77510991078899278489fd8f
6 https://api.github.com/repos/tidyverse/dplyr/commits/e0555277878ed174d40bda86690674ecdc27db55
                                                                            html_url
1 https://github.com/tidyverse/dplyr/commit/fb25640fa1eb74746a7a74a06090045106e5d20f
2 https://github.com/tidyverse/dplyr/commit/e4e9a295a373b85e02ae084a23f12e9212a72b98
3 https://github.com/tidyverse/dplyr/commit/1d17672a54305170dc75c251f8ae69a85c0bea37
4 https://github.com/tidyverse/dplyr/commit/cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84
5 https://github.com/tidyverse/dplyr/commit/85e94fcde02ad49c77510991078899278489fd8f
6 https://github.com/tidyverse/dplyr/commit/e0555277878ed174d40bda86690674ecdc27db55
                                                                                            comments_url
1 https://api.github.com/repos/tidyverse/dplyr/commits/fb25640fa1eb74746a7a74a06090045106e5d20f/comments
2 https://api.github.com/repos/tidyverse/dplyr/commits/e4e9a295a373b85e02ae084a23f12e9212a72b98/comments
3 https://api.github.com/repos/tidyverse/dplyr/commits/1d17672a54305170dc75c251f8ae69a85c0bea37/comments
4 https://api.github.com/repos/tidyverse/dplyr/commits/cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84/comments
5 https://api.github.com/repos/tidyverse/dplyr/commits/85e94fcde02ad49c77510991078899278489fd8f/comments
6 https://api.github.com/repos/tidyverse/dplyr/commits/e0555277878ed174d40bda86690674ecdc27db55/comments
  author.login author.id       author.node_id
1       krlmlr   1741643 MDQ6VXNlcjE3NDE2NDM=
2 DavisVaughan  19150088 MDQ6VXNlcjE5MTUwMDg4
3    ilovemane  58779940 MDQ6VXNlcjU4Nzc5OTQw
4  apeterson91   4251291 MDQ6VXNlcjQyNTEyOTE=
5    nmercadeb  61558739 MDQ6VXNlcjYxNTU4NzM5
6   JamesHWade   6314313 MDQ6VXNlcjYzMTQzMTM=
                                     author.avatar_url author.gravatar_id
1  https://avatars.githubusercontent.com/u/1741643?v=4                   
2 https://avatars.githubusercontent.com/u/19150088?v=4                   
3 https://avatars.githubusercontent.com/u/58779940?v=4                   
4  https://avatars.githubusercontent.com/u/4251291?v=4                   
5 https://avatars.githubusercontent.com/u/61558739?v=4                   
6  https://avatars.githubusercontent.com/u/6314313?v=4                   
                                 author.url                 author.html_url
1       https://api.github.com/users/krlmlr       https://github.com/krlmlr
2 https://api.github.com/users/DavisVaughan https://github.com/DavisVaughan
3    https://api.github.com/users/ilovemane    https://github.com/ilovemane
4  https://api.github.com/users/apeterson91  https://github.com/apeterson91
5    https://api.github.com/users/nmercadeb    https://github.com/nmercadeb
6   https://api.github.com/users/JamesHWade   https://github.com/JamesHWade
                                 author.followers_url
1       https://api.github.com/users/krlmlr/followers
2 https://api.github.com/users/DavisVaughan/followers
3    https://api.github.com/users/ilovemane/followers
4  https://api.github.com/users/apeterson91/followers
5    https://api.github.com/users/nmercadeb/followers
6   https://api.github.com/users/JamesHWade/followers
                                              author.following_url
1       https://api.github.com/users/krlmlr/following{/other_user}
2 https://api.github.com/users/DavisVaughan/following{/other_user}
3    https://api.github.com/users/ilovemane/following{/other_user}
4  https://api.github.com/users/apeterson91/following{/other_user}
5    https://api.github.com/users/nmercadeb/following{/other_user}
6   https://api.github.com/users/JamesHWade/following{/other_user}
                                           author.gists_url
1       https://api.github.com/users/krlmlr/gists{/gist_id}
2 https://api.github.com/users/DavisVaughan/gists{/gist_id}
3    https://api.github.com/users/ilovemane/gists{/gist_id}
4  https://api.github.com/users/apeterson91/gists{/gist_id}
5    https://api.github.com/users/nmercadeb/gists{/gist_id}
6   https://api.github.com/users/JamesHWade/gists{/gist_id}
                                                author.starred_url
1       https://api.github.com/users/krlmlr/starred{/owner}{/repo}
2 https://api.github.com/users/DavisVaughan/starred{/owner}{/repo}
3    https://api.github.com/users/ilovemane/starred{/owner}{/repo}
4  https://api.github.com/users/apeterson91/starred{/owner}{/repo}
5    https://api.github.com/users/nmercadeb/starred{/owner}{/repo}
6   https://api.github.com/users/JamesHWade/starred{/owner}{/repo}
                                 author.subscriptions_url
1       https://api.github.com/users/krlmlr/subscriptions
2 https://api.github.com/users/DavisVaughan/subscriptions
3    https://api.github.com/users/ilovemane/subscriptions
4  https://api.github.com/users/apeterson91/subscriptions
5    https://api.github.com/users/nmercadeb/subscriptions
6   https://api.github.com/users/JamesHWade/subscriptions
                        author.organizations_url
1       https://api.github.com/users/krlmlr/orgs
2 https://api.github.com/users/DavisVaughan/orgs
3    https://api.github.com/users/ilovemane/orgs
4  https://api.github.com/users/apeterson91/orgs
5    https://api.github.com/users/nmercadeb/orgs
6   https://api.github.com/users/JamesHWade/orgs
                                 author.repos_url
1       https://api.github.com/users/krlmlr/repos
2 https://api.github.com/users/DavisVaughan/repos
3    https://api.github.com/users/ilovemane/repos
4  https://api.github.com/users/apeterson91/repos
5    https://api.github.com/users/nmercadeb/repos
6   https://api.github.com/users/JamesHWade/repos
                                           author.events_url
1       https://api.github.com/users/krlmlr/events{/privacy}
2 https://api.github.com/users/DavisVaughan/events{/privacy}
3    https://api.github.com/users/ilovemane/events{/privacy}
4  https://api.github.com/users/apeterson91/events{/privacy}
5    https://api.github.com/users/nmercadeb/events{/privacy}
6   https://api.github.com/users/JamesHWade/events{/privacy}
                                 author.received_events_url author.type
1       https://api.github.com/users/krlmlr/received_events        User
2 https://api.github.com/users/DavisVaughan/received_events        User
3    https://api.github.com/users/ilovemane/received_events        User
4  https://api.github.com/users/apeterson91/received_events        User
5    https://api.github.com/users/nmercadeb/received_events        User
6   https://api.github.com/users/JamesHWade/received_events        User
  author.user_view_type author.site_admin committer.login committer.id
1                public             FALSE          krlmlr      1741643
2                public             FALSE        web-flow     19864447
3                public             FALSE        web-flow     19864447
4                public             FALSE        web-flow     19864447
5                public             FALSE        web-flow     19864447
6                public             FALSE        web-flow     19864447
     committer.node_id                                 committer.avatar_url
1 MDQ6VXNlcjE3NDE2NDM=  https://avatars.githubusercontent.com/u/1741643?v=4
2 MDQ6VXNlcjE5ODY0NDQ3 https://avatars.githubusercontent.com/u/19864447?v=4
3 MDQ6VXNlcjE5ODY0NDQ3 https://avatars.githubusercontent.com/u/19864447?v=4
4 MDQ6VXNlcjE5ODY0NDQ3 https://avatars.githubusercontent.com/u/19864447?v=4
5 MDQ6VXNlcjE5ODY0NDQ3 https://avatars.githubusercontent.com/u/19864447?v=4
6 MDQ6VXNlcjE5ODY0NDQ3 https://avatars.githubusercontent.com/u/19864447?v=4
  committer.gravatar_id                         committer.url
1                         https://api.github.com/users/krlmlr
2                       https://api.github.com/users/web-flow
3                       https://api.github.com/users/web-flow
4                       https://api.github.com/users/web-flow
5                       https://api.github.com/users/web-flow
6                       https://api.github.com/users/web-flow
           committer.html_url                         committer.followers_url
1   https://github.com/krlmlr   https://api.github.com/users/krlmlr/followers
2 https://github.com/web-flow https://api.github.com/users/web-flow/followers
3 https://github.com/web-flow https://api.github.com/users/web-flow/followers
4 https://github.com/web-flow https://api.github.com/users/web-flow/followers
5 https://github.com/web-flow https://api.github.com/users/web-flow/followers
6 https://github.com/web-flow https://api.github.com/users/web-flow/followers
                                       committer.following_url
1   https://api.github.com/users/krlmlr/following{/other_user}
2 https://api.github.com/users/web-flow/following{/other_user}
3 https://api.github.com/users/web-flow/following{/other_user}
4 https://api.github.com/users/web-flow/following{/other_user}
5 https://api.github.com/users/web-flow/following{/other_user}
6 https://api.github.com/users/web-flow/following{/other_user}
                                    committer.gists_url
1   https://api.github.com/users/krlmlr/gists{/gist_id}
2 https://api.github.com/users/web-flow/gists{/gist_id}
3 https://api.github.com/users/web-flow/gists{/gist_id}
4 https://api.github.com/users/web-flow/gists{/gist_id}
5 https://api.github.com/users/web-flow/gists{/gist_id}
6 https://api.github.com/users/web-flow/gists{/gist_id}
                                         committer.starred_url
1   https://api.github.com/users/krlmlr/starred{/owner}{/repo}
2 https://api.github.com/users/web-flow/starred{/owner}{/repo}
3 https://api.github.com/users/web-flow/starred{/owner}{/repo}
4 https://api.github.com/users/web-flow/starred{/owner}{/repo}
5 https://api.github.com/users/web-flow/starred{/owner}{/repo}
6 https://api.github.com/users/web-flow/starred{/owner}{/repo}
                          committer.subscriptions_url
1   https://api.github.com/users/krlmlr/subscriptions
2 https://api.github.com/users/web-flow/subscriptions
3 https://api.github.com/users/web-flow/subscriptions
4 https://api.github.com/users/web-flow/subscriptions
5 https://api.github.com/users/web-flow/subscriptions
6 https://api.github.com/users/web-flow/subscriptions
                 committer.organizations_url
1   https://api.github.com/users/krlmlr/orgs
2 https://api.github.com/users/web-flow/orgs
3 https://api.github.com/users/web-flow/orgs
4 https://api.github.com/users/web-flow/orgs
5 https://api.github.com/users/web-flow/orgs
6 https://api.github.com/users/web-flow/orgs
                          committer.repos_url
1   https://api.github.com/users/krlmlr/repos
2 https://api.github.com/users/web-flow/repos
3 https://api.github.com/users/web-flow/repos
4 https://api.github.com/users/web-flow/repos
5 https://api.github.com/users/web-flow/repos
6 https://api.github.com/users/web-flow/repos
                                    committer.events_url
1   https://api.github.com/users/krlmlr/events{/privacy}
2 https://api.github.com/users/web-flow/events{/privacy}
3 https://api.github.com/users/web-flow/events{/privacy}
4 https://api.github.com/users/web-flow/events{/privacy}
5 https://api.github.com/users/web-flow/events{/privacy}
6 https://api.github.com/users/web-flow/events{/privacy}
                          committer.received_events_url committer.type
1   https://api.github.com/users/krlmlr/received_events           User
2 https://api.github.com/users/web-flow/received_events           User
3 https://api.github.com/users/web-flow/received_events           User
4 https://api.github.com/users/web-flow/received_events           User
5 https://api.github.com/users/web-flow/received_events           User
6 https://api.github.com/users/web-flow/received_events           User
  committer.user_view_type committer.site_admin
1                   public                FALSE
2                   public                FALSE
3                   public                FALSE
4                   public                FALSE
5                   public                FALSE
6                   public                FALSE
                                                                                                                                                                                                                      parents
1 e4e9a295a373b85e02ae084a23f12e9212a72b98, https://api.github.com/repos/tidyverse/dplyr/commits/e4e9a295a373b85e02ae084a23f12e9212a72b98, https://github.com/tidyverse/dplyr/commit/e4e9a295a373b85e02ae084a23f12e9212a72b98
2 1d17672a54305170dc75c251f8ae69a85c0bea37, https://api.github.com/repos/tidyverse/dplyr/commits/1d17672a54305170dc75c251f8ae69a85c0bea37, https://github.com/tidyverse/dplyr/commit/1d17672a54305170dc75c251f8ae69a85c0bea37
3 cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84, https://api.github.com/repos/tidyverse/dplyr/commits/cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84, https://github.com/tidyverse/dplyr/commit/cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84
4 85e94fcde02ad49c77510991078899278489fd8f, https://api.github.com/repos/tidyverse/dplyr/commits/85e94fcde02ad49c77510991078899278489fd8f, https://github.com/tidyverse/dplyr/commit/85e94fcde02ad49c77510991078899278489fd8f
5 e0555277878ed174d40bda86690674ecdc27db55, https://api.github.com/repos/tidyverse/dplyr/commits/e0555277878ed174d40bda86690674ecdc27db55, https://github.com/tidyverse/dplyr/commit/e0555277878ed174d40bda86690674ecdc27db55
6 173b4232bff810f563e1739211ea7545ed0651e6, https://api.github.com/repos/tidyverse/dplyr/commits/173b4232bff810f563e1739211ea7545ed0651e6, https://github.com/tidyverse/dplyr/commit/173b4232bff810f563e1739211ea7545ed0651e6

Then, we can extract specific data from the commits including details like the commit message, author, and date. We will create a simplified data frame with just these columns.

commits_df <- tibble(
    sha = commits$sha,
    author = commits$commit$author$name,
    date = commits$commit$author$date,
    message = commits$commit$message)
commits_df
# A tibble: 30 × 4
   sha                                      author               date    message
   <chr>                                    <chr>                <chr>   <chr>  
 1 fb25640fa1eb74746a7a74a06090045106e5d20f Kirill Müller        2024-1… "Move …
 2 e4e9a295a373b85e02ae084a23f12e9212a72b98 Davis Vaughan        2024-1… "Remov…
 3 1d17672a54305170dc75c251f8ae69a85c0bea37 Mike Du              2024-0… "Add e…
 4 cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84 Adam                 2024-0… "Add d…
 5 85e94fcde02ad49c77510991078899278489fd8f Núria Mercadé-Besora 2024-0… "Docum…
 6 e0555277878ed174d40bda86690674ecdc27db55 James Wade           2024-0… "Add a…
 7 173b4232bff810f563e1739211ea7545ed0651e6 Kirill Müller        2024-0… "Fix e…
 8 b2c7e047081301afe2ca376c508491d77bea4bde Jeremy Winget        2024-0… "Fix `…
 9 663d7f07140b45d5d3a256465728d9d5c35b9455 Rodrigo Dal Ben      2024-0… "Fix t…
10 cdc99196a2d80f14b48ceb5edad328ab34a5bf65 Davis Vaughan        2024-0… "Add `…
# ℹ 20 more rows

Authentication

Authenticating with the GitHub API via an API key allows you to send much more requests to the API.

API access keys for the GitHub API are called personal access tokens (PAT) and the documentation explains how to generate a PAT once you have logged into your GitHub account.

To create a PAT: You can create a PAT from your GitHub account (Settings > Developer settings > Personal access tokens). It’s a good idea to only grant “read” permissions.

Where to store API keys

First, please be careful with your PATs and never publish them.

It is suggested you keep them in your .Renviron file which looks something like this on the inside:

GITHUB_PAT = <add my GitHub PAT here> 

If you do not have an .Renviron file in your home directory, you can make one:

cd ~
touch .Renviron

If you want additional guidance on where you should store them, I like this post:

Assuming you have created and stored an API key in the .Renviron file in your home directory, you can fetch it with the Sys.getenv() function and use the PAT in our httr2 request.

# Read the PAT from environment variables
github_pat <- Sys.getenv("GITHUB_PAT")

# Make the GET request with PAT for authentication
response <- request(url) %>%
  req_auth_bearer_token(github_pat) %>%
  req_perform()

Now, we check the response as before

if (resp_status(response) == 200) {
  commits <- resp_body_json(response, simplifyVector = TRUE)
  commits_df <- tibble(
    sha = commits$sha,
    author = commits$commit$author$name,
    date = commits$commit$author$date,
    message = commits$commit$message)
  commits_df
} else {
  message("Failed to retrieve data. Status code: ", resp_status(response))
}
# A tibble: 30 × 4
   sha                                      author               date    message
   <chr>                                    <chr>                <chr>   <chr>  
 1 fb25640fa1eb74746a7a74a06090045106e5d20f Kirill Müller        2024-1… "Move …
 2 e4e9a295a373b85e02ae084a23f12e9212a72b98 Davis Vaughan        2024-1… "Remov…
 3 1d17672a54305170dc75c251f8ae69a85c0bea37 Mike Du              2024-0… "Add e…
 4 cfb25a030f9f7c39f77fed2c97f3fa7b15a55e84 Adam                 2024-0… "Add d…
 5 85e94fcde02ad49c77510991078899278489fd8f Núria Mercadé-Besora 2024-0… "Docum…
 6 e0555277878ed174d40bda86690674ecdc27db55 James Wade           2024-0… "Add a…
 7 173b4232bff810f563e1739211ea7545ed0651e6 Kirill Müller        2024-0… "Fix e…
 8 b2c7e047081301afe2ca376c508491d77bea4bde Jeremy Winget        2024-0… "Fix `…
 9 663d7f07140b45d5d3a256465728d9d5c35b9455 Rodrigo Dal Ben      2024-0… "Fix t…
10 cdc99196a2d80f14b48ceb5edad328ab34a5bf65 Davis Vaughan        2024-0… "Add `…
# ℹ 20 more rows

Example: Learn about Stephanie

Let’s start by using the GitHub API to learn information about myself (Stephanie Hicks). First, let’s check out https://api.github.com/users/stephaniehicks.

Now, we have decided to explore https://api.github.com/users/stephaniehicks/repos.

To use httr2, start by creating a request:

owner <- "stephaniehicks"
url <- paste0("https://api.github.com/users/", owner,"/repos")

# Make the GET request with PAT for authentication
response <- request(url) %>%
  req_auth_bearer_token(github_pat) %>%
  req_perform()

stephanie <- resp_body_json(response, simplifyVector = TRUE)

We convert the response to a parsed JSON file (or a list).

A bit of EDA fun

Let’s have a bit of fun and explore some questions:

  • How many public repositories do I have?
stephanie$forks
 [1]  4  0  1  0  1  0  0  2  0  0  0  4  1  1  0  1 11  0  0  3  0  3  0  1  1
[26]  0  0  1  0  0

What’s the most popular language?

table(stephanie$language)

            HTML Jupyter Notebook                R             Ruby 
               7                1               12                2 
             TeX 
               1 

To find out how many repos that I have with open issues, we can just create a table:

# how many repos have open issues? 
table(stephanie$open_issues)

 0  1 
27  3 

Whew! Not as many as I thought.

Other examples with GitHub API