Tracking the ISS from R: A Tutorial on the Open Notify API

Author

James Adhiambo

Published

November 3, 2026

1. What is Open Notify and why use it?

Open Notify is a free, open-source project that turns raw NASA data into simple web APIs about space and spacecraft. It has no sign-up, no API key and no cost, which makes it a great first API to learn.

This tutorial uses two of its endpoints:

Endpoint What it returns
/iss-now.json The current latitude and longitude of the International Space Station
/astros.json The number of people currently in space, with their names and spacecraft

Why would someone use it?

  • Build a live ISS tracker or map
  • Add real space data to a dashboard or class project
  • Practice working with a REST API and JSON with no account setup

Why it is a good fit for R: the data arrives as JSON, which R can turn into a tidy data frame, and the location data is easy to plot on a map.

I picked Open Notify because it needs no account or API key, so I could start making calls right away, and I’m deeply interested in astronomy.

2. Setting up the API

On the service’s website

There is nothing to set up. Open Notify does not require an account, an API key or authentication. You can read the documentation at http://open-notify.org/Open-Notify-API/.

Two things from the docs worth knowing:

  • Each endpoint is a simple GET request to a URL, for example http://api.open-notify.org/iss-now.json.
  • The ISS position is only calculated once per second, and the site asks users to poll about once every 5 seconds at most.

In R

Step 1: Install the packages (once).

install.packages(c("httr2", "dplyr", "readr", "tibble", "tidyverse", "maps"))

Step 2: Load them.

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

Step 3: Build a request. httr2 lets you build a request in pieces. The base URL is the same for every endpoint, and req_url_path() picks which endpoint to call.

base_url <- "http://api.open-notify.org"

req <- request(base_url) |>
  req_url_path("iss-now.json")

Step 4: Send it and read the JSON.

resp <- req_perform(req)
x <- resp_body_json(resp)
x

The response is a list with a message, a Unix timestamp, and an iss_position containing latitude and longitude.

Note

Why the API calls are not run in this document. API results change every time they run. Following good practice, the calls live in a separate script, fetch_iss_data.R, which saves the results as CSV files. This document then loads those CSVs with read_csv().

3. Demonstration

Call 1: Where is the ISS?

This function (from fetch_iss_data.R) makes one call and turns the response into a one-row data frame. Latitude and longitude come back as text, so they are converted to numbers.

get_iss_position <- function() {
  resp <- request(base_url) |>
    req_url_path("iss-now.json") |>
    req_perform()

  x <- resp_body_json(resp)

  tibble(
    timestamp = as.POSIXct(x$timestamp, origin = "1970-01-01", tz = "UTC"),
    latitude  = as.numeric(x$iss_position$latitude),
    longitude = as.numeric(x$iss_position$longitude)
  )
}

The script called this function 10 times, 30 seconds apart, and saved the results. Here is the saved data:

iss_track <- read_csv("data/iss_track.csv", show_col_types = FALSE)
kable(iss_track)
timestamp latitude longitude
2026-11-04 03:10:43 18.6664 39.0334
2026-11-04 03:11:13 17.1691 40.2596
2026-11-04 03:11:44 15.6629 41.4640
2026-11-04 03:12:14 14.1488 42.6488
2026-11-04 03:12:45 12.6277 43.8160
2026-11-04 03:13:16 11.1006 44.9678
2026-11-04 03:13:46 9.5683 46.1061
2026-11-04 03:14:17 8.0316 47.2328
2026-11-04 03:14:47 6.4911 48.3501
2026-11-04 03:15:18 4.9477 49.4597

And here is where those readings fall on a world map:

world <- map_data("world")

ggplot() +
  geom_polygon(data = world, aes(long, lat, group = group),
               fill = "grey85", color = "white", linewidth = 0.2) +
  geom_point(data = iss_track, aes(longitude, latitude, color = timestamp),
             size = 3) +
  coord_quickmap() +
  labs(title = "ISS readings over about 5 minutes",
       x = "Longitude", y = "Latitude", color = "Time (UTC)") +
  theme_minimal()

Call 2: Who is in space right now?

The second endpoint takes no inputs either. The script saved the response to a CSV:

people <- read_csv("data/people_in_space.csv", show_col_types = FALSE)
kable(people |> select(name, craft))
name craft
Oleg Kononenko ISS
Nikolai Chub ISS
Tracy Caldwell Dyson ISS
Matthew Dominick ISS
Michael Barratt ISS
Jeanette Epps ISS
Alexander Grebenkin ISS
Butch Wilmore ISS
Sunita Williams ISS
Li Guangsu Tiangong
Li Cong Tiangong
Ye Guangfu Tiangong
Warning

Data caveat: The crew list returned by Open Notify may be out of date, so check it against NASA’s website before relying on it. This is a good reminder to verify an API’s data before using it.

A quick summary by spacecraft:

people |> count(craft, name = "people")
# A tibble: 2 × 2
  craft    people
  <chr>     <int>
1 ISS           9
2 Tiangong      3

4. Wrap-up

  • Open Notify is free and needs no API key, so it is a low-friction way to practice calling an API from R.
  • httr2 builds the request, resp_body_json() reads the JSON, and tibble() turns it into a data frame you can analyze or map.
  • Be polite to the server: keep polling to about once every 5 seconds or slower.

Source: Open Notify, http://open-notify.org (data derived from NASA).