
Building the R Package ipapi.r: A Journey to Simplify IP Address Lookups
Introduction: The Motivation Behind the Project

As data analysis increasingly relies on location-based insights, the need to retrieve detailed information about IP addresses—such as geographic location, internet service provider (ISP), and timezone—has grown significantly.
with these information, user achive “security work” like detecting Spam, Fraudsters, or Blocking webcrawlers.
However, R (or shiny) is basically not intended to those work instead of statistical things. This gap inspired the creation of {ipapi.r}, an R package designed to streamline IP address lookups using the IPQuery API.
Introducing {ipapi.r}
What is {ipapi.r}?
{ipapi.r} is an R package that allows users to retrieve detailed information about IP addresses, including country, city, region, ISP, and more informations with just a few lines of code.
Key Features
- Single or batch IP address lookups.
- Support for API key integration to handle higher request limits.
- Easy-to-use functions that return structured data frames, json, yaml and so on.
# install.packages("pak")
pak::pak("jahnen/ipapi.r")
library(ipapi.r)
# Getting Your Own IP
IPQuery()
# Query Own IP
IPQuery("self")
# Query details for a specific IP
IPQuery("1.1.1.1")
# Querying Multiple IPs
IPQuery(c("1.1.1.1", "2.2.2.2")))
The Development Process
Using this {ipapi.r} package is straightforward because the original API is well designed.
So now I’m going to walk you through the process of creating this R package.
File > New project > New Directory > R package > Create

or you can use usethis::create_package()
.
Implement API in R as functions
the original IPQuery API has 3 main functions. See docs
- Request (user’s) IP address
- Request Specific IP address (like
1.1.1.1
) - Request Multiple IP addresses (like
1.1.1.1 , 2.2.2.2
)
Also result can have various format: Plain Text
, XML
, YAML
, JSON
Getting this result in R can be done by using httr2
package.
So let’s make first funciton, that returns user’s IP address.
It will be like this:
request
https://api.ipquery.io/
and return result.
library(httr2)
IPQuery <- function(){
"https://api.ipquery.io/" |>
request() |>
req_perform() |>
resp_body_string()
}
However, using library
in R package is not recommended.
so let’s call it as using roxygen2
(@importFrom
or import
)
#' @importFrom httr2 request req_perform resp_body_string
#'
and it will return user’s (my blurred) IP address.

Next, we need to make function that returns specific IP address. Which can be done by accessing same URL but giving IP as query parameter. So we need to change function to get input (the IP address)
# "https://api.ipquery.io/1.1.1.1"
IPQuery <- function(ip = NULL){
paste0("https://api.ipquery.io/", ip) |>
request() |>
req_perform() |>
resp_body_string()
}
Note that, IP is not required, (for get self IP address) so use default value as NULL
.

Now, we need to make function that returns multiple IP addresses. So we need to implement code for handling multiple IP addresses using lapply
and return as list
.
Assume that input IP
has format of vector
like c("1.1.1.1", "2.2.2.2")
.
# https://api.ipquery.io/1.1.1.1,2.2.2.2
IPQuery <- function(ip = NULL) {
baseURL <- "https://api.ipquery.io/"
if (is.null(ip)) {
ip <- ""
} else if (length(ip) > 1) {
return(lapply(ip, function(single_ip) {
query(paste0(baseURL, single_ip))
}))
} else if(ip == 'self'){
ip <- IPQuery()
}
resp <- query(paste0(baseURL, ip))
return(resp)
}
query <- function(i) {
i |>
request() |>
req_perform() |>
resp_body_string()
}
Note that, query
function used in IPQuery
function is just for handling single IP address.

And finally, we need to handle the result format. which is just text
now. but we can add format
parameter to handle it. (yaml
, json
, xml
)
# https://api.ipquery.io/1.1.1.1,2.2.2.2?format=xml
IPQuery <- function(ip = NULL, format = "text") {
baseURL <- "https://api.ipquery.io/"
format_query <- ifelse(format == "text", "", paste0("?format=", format))
if (is.null(ip)) {
ip <- ""
} else if (length(ip) > 1) {
return(lapply(ip, function(single_ip) {
query(paste0(baseURL, single_ip, format_query))
}))
} else if(ip == 'self'){
ip <- IPQuery()
}
resp <- query(paste0(baseURL, ip, format_query))
return(resp)
}

Add unit test
Finally, we’ve implemented very basic function of IPQuery api in R.
So let’s make testing for this with {testthat}. (usethis::use_testthat()
)
It’s hard to define an exact test, but the {ipapi.r} package provides a core functionality.
- for each format,
- for each format. 3. for each example IP (1.1.1.1, 2.2.2.2),
- and if it gives an error when entering a non-IP value.
test_that("query works", {
expect_equal(substr(query("1.1.1.1"), 1, 15), "<!DOCTYPE html>")
})
test_that("error with invalid url", {
expect_error(query("invalid_url"))
})
test_that("IPQuery text format", {
expect_equal(IPQuery(), query("https://api.ipify.org/"))
expect_equal(substr(IPQuery("self"), 1, 5), "{\"ip\"")
expect_equal(substr(IPQuery(c("1.1.1.1")), 1, 14), "{\"ip\":\"1.1.1.1")
expect_equal(length(IPQuery(c("1.1.1.1", "2.2.2.2"))), 2)
})
# Skip for other formats, see https://github.com/jahnen/ipapi.r/blob/main/tests/testthat/test-IPQuery.R
Conclusion
{ipapi.r} is a wrapper R package for R users who need to retrieve IP address information quickly and efficiently. By addressing common challenges and incorporating user feedback, the package continues to evolve, offering a reliable solution for location-based data analysis.
Future Plans
since IPQuery api is simple and powerful, There’s not much we can do in {ipapi.r}.
Below may be considered, but you can always make suggestion:
- Seamless connectivity with packages like {jsonlite} to better utilize each format.