An R package for interacting with the Wildbook v3 API. This package provides an interface for authenticating with Wildbook instances and searching for encounters, individuals, and other data. For a Python client with aligned functionality see pywildbook.
Many thanks to Simon Bonner for the conception and development of this project for the Wildbook v1 API.
# Install dependencies first
install.packages(c("httr2", "R6"))
# Install from local directory
install.packages(".", repos = NULL, type = "source")This is the recommended approach for a clean build that includes vignettes:
# 1. Build the source package (tarball)
R CMD build .
# 2. Install the resulting tarball
R CMD INSTALL RWildbook_1.0.0.tar.gzNote on Vignettes: Building the vignettes requires Pandoc to be installed on your system.
- On macOS, you can install it via Homebrew:
brew install pandoc - On Windows/Linux, see Pandoc installation guide.
If you do not have Pandoc installed, you can build and install without vignettes:
R CMD build . --no-build-vignettes
R CMD INSTALL RWildbook_1.0.0.tar.gzlibrary(RWildbook)library(RWildbook)
# Define null-coalescing operator if not already available
`%||%` <- function(x, y) if (is.null(x)) y else x
# Create a client instance
# The base URL can also be set via the WILDBOOK_URL environment variable
client <- WildbookClient$new(Sys.getenv("WILDBOOK_URL", "http://localhost:8080"))
# Login
# Credentials can be passed directly or sourced from WILDBOOK_USERNAME and WILDBOOK_PASSWORD environment variables
client$login()
# Search for encounters
query <- match_all()
results <- client$search_encounters(query, size = 10)
# Print results
for (encounter in results$hits) {
cat(sprintf("%s: %s %s\n",
encounter$id,
encounter$genus,
encounter$specificEpithet %||% ""))
}
# Logout when done
client$logout()The client uses session-based authentication. After logging in, the session cookie is automatically managed for all subsequent requests. For security, it is highly recommended to use environment variables for sensitive credentials.
# Create client
# The base URL can also be set via the WILDBOOK_URL environment variable
client <- WildbookClient$new(Sys.getenv("WILDBOOK_URL", "http://localhost:8080"))
# Login using environment variables (recommended)
# Credentials sourced from WILDBOOK_USERNAME and WILDBOOK_PASSWORD environment variables
user_info <- client$login()
# Logged in successfully as: user@example.com
# Or login with explicit credentials
user_info <- client$login("user@example.com", "password")
cat(sprintf("User ID: %s\n", user_info$id))
cat(sprintf("Full Name: %s\n", user_info$fullName))
# Check authentication status
if (client$is_authenticated()) {
cat("✓ Authenticated\n")
}
# Get current user info
user <- client$get_current_user()
# Logout
client$logout()# Get all encounters
results <- client$search_encounters(match_all(), size = 50)
# With pagination
results <- client$search_encounters(
match_all(),
from = 0, # offset
size = 20, # page size
sort = "year", # sort field
sort_order = "desc"
)# Get my 10 most recent encounters
my_encounters <- client$search_encounters(
client$filter_current_user(),
size = 10,
sort = "year",
sort_order = "desc"
)
# Combine with other filters: my encounters of a specific species
query <- combine_queries(
client$filter_current_user(),
filter_by_species("Megaptera novaeangliae"),
operator = "must"
)
results <- client$search_encounters(query)# Search by species
query <- filter_by_species("novaeangliae")
results <- client$search_encounters(query)
# Search by genus and species
query <- filter_by_species("Megaptera novaeangliae")
results <- client$search_encounters(query)# Encounters since 1 November 2025
query <- filter_by_date_range(start_date = "2025-11-01")
results <- client$search_encounters(query)
# Encounters between two dates
query <- filter_by_date_range(
start_date = "2025-11-01",
end_date = "2025-12-01"
)
results <- client$search_encounters(query)# Filter by country
query <- filter_by_location(country = "Kenya")
results <- client$search_encounters(query)
# Filter by bounding box
query <- filter_by_location(
min_lat = -5.0,
max_lat = 5.0,
min_lon = 35.0,
max_lon = 42.0
)
results <- client$search_encounters(query)# Female humpback whales from 2020-2023
species <- filter_by_species("Megaptera novaeangliae")
sex <- filter_by_sex("female")
years <- filter_by_year_range(2020, 2023)
query <- combine_queries(species, sex, years, operator = "must")
results <- client$search_encounters(query)# Encounters without an assigned individual
query <- field_missing("individualId")
unassigned <- client$search_encounters(query)# Search for "beach" in locality descriptions
query <- text_search("verbatimLocality", "beach", fuzzy = TRUE)
results <- client$search_encounters(query)# Find individuals with encounters
query <- field_exists("encounters")
results <- client$search_individuals(query, size = 20)
for (individual in results$hits) {
cat(sprintf("%s: %s\n",
individual$id,
individual$displayName %||% "Unnamed"))
}# Get a specific encounter by UUID
encounter <- client$get_encounter("123e4567-e89b-12d3-a456-426614174000")
print(encounter)
# Get a specific individual by UUID
individual <- client$get_individual("987fcdeb-51a2-43f7-9876-543210fedcba")
print(individual)# Get dashboard data for the current user
dashboard <- client$get_user_home()
cat(sprintf("Latest encounters: %d\n", length(dashboard$latestEncounters)))
cat(sprintf("Projects: %d\n", length(dashboard$projects)))The package provides these helper functions for constructing queries:
match_all()- Match all documentsfilter_by_sex(sex)- Filter by sexfilter_by_species(species, genus = NULL)- Filter by speciesfilter_by_year_range(start_year, end_year)- Filter by year rangefilter_by_date_range(start_date, end_date)- Filter by date range (ISO 8601)filter_by_location(country, location_id, min_lat, max_lat, min_lon, max_lon)- Filter by locationfilter_by_individual(individual_id)- Find encounters for an individualfilter_by_submitter(submitter_id)- Filter by submittertext_search(field, text, fuzzy = FALSE)- Text search in a fieldfield_exists(field)- Find documents where field existsfield_missing(field)- Find documents where field is missingcombine_queries(..., operator = "must")- Combine multiple queries with AND/OR/NOT logic
For advanced use cases, you can construct your own OpenSearch/Elasticsearch queries:
# Custom query using Elasticsearch DSL
custom_query <- list(
bool = list(
must = list(
list(term = list(genus = "Tursiops")),
list(range = list(year = list(gte = 2020, lte = 2023)))
),
must_not = list(
list(term = list(sex = "unknown"))
)
)
)
results <- client$search_encounters(custom_query)Results are returned as R lists. You can convert them to data frames for easier analysis:
library(dplyr)
library(purrr)
# Search for encounters
query <- match_all()
results <- client$search_encounters(query, size = 100)
# Convert to data frame
encounters_df <- map_dfr(results$hits, function(hit) {
tibble(
id = hit$id %||% NA,
genus = hit$genus %||% NA,
species = hit$specificEpithet %||% NA,
year = hit$year %||% NA,
sex = hit$sex %||% NA,
locality = hit$verbatimLocality %||% NA,
lat = hit$decimalLatitude %||% NA,
lon = hit$decimalLongitude %||% NA
)
})
# Analyze
encounters_df %>%
group_by(genus, species) %>%
summarise(count = n()) %>%
arrange(desc(count))RWildbook raises typed conditions that can be caught by class name, giving you precise control over error handling.
| Class | Raised when |
|---|---|
wildbook_error |
Base class — catches any Wildbook error |
wildbook_auth_error |
Server returns 401, or login returns success=FALSE |
wildbook_not_authenticated |
Method called before login() |
wildbook_forbidden |
Server returns 403 |
wildbook_not_found |
Server returns 404 |
wildbook_bad_request |
Server returns 400 |
wildbook_api_error |
Any other HTTP error |
tryCatch(
client$get_encounter("some-uuid"),
wildbook_not_found = function(e) cat("Not found:", e$message, "\n"),
wildbook_auth_error = function(e) cat("Re-authentication required\n"),
wildbook_error = function(e) cat("Other Wildbook error:", e$message, "\n")
)tryCatch(
client$search_encounters(match_all()),
wildbook_error = function(e) {
cat("Wildbook error:", e$message, "\n")
}
)Use with_wildbook_client() instead of a manual tryCatch/finally block to guarantee logout on both success and error:
# Credentials from WILDBOOK_URL, WILDBOOK_USERNAME, WILDBOOK_PASSWORD env vars
with_wildbook_client(\(client) {
results <- client$search_encounters(match_all(), size = 10)
results$hits
})The client is always logged out when the function exits, even if an error occurs inside.
See the examples/ directory for complete examples:
basic_usage.R- Basic login, search, and logoutadvanced_search.R- Complex queries, pagination, and filtering
Run examples:
# Set environment variables
export WILDBOOK_URL="http://localhost:8080"
export WILDBOOK_USERNAME="your@email.com"
export WILDBOOK_PASSWORD="yourpassword"
# Run in R
Rscript examples/basic_usage.R
Rscript examples/advanced_search.RThe package includes vignettes demonstrating common use cases:
- Individual Statistics - Analyze encounter patterns, count distinct individuals by species, and identify frequently encountered individuals
View vignettes:
# List all vignettes
browseVignettes("RWildbook")
# Open a specific vignette
vignette("individual_statistics", package = "RWildbook")See CONTRIBUTING.md for contributor workflow, coding conventions, tests, and pull request guidance.
RWildbook/
├── R/
│ ├── client.R # WildbookClient R6 class
│ ├── conditions.R # Typed error condition hierarchy
│ └── queries.R # Query helper functions
├── man/ # Roxygen2-generated documentation
├── examples/
│ ├── basic_usage.R
│ └── advanced_search.R
├── tests/ # Test files
├── DESCRIPTION # Package metadata
├── NAMESPACE # Package exports
└── README.md
# Install roxygen2
install.packages("roxygen2")
# Generate documentation
roxygen2::roxygenize()# Install testthat
install.packages("testthat")
# Run tests
testthat::test_dir("tests")R6 class for interacting with Wildbook.
$new(base_url)- Create a new client instance$login(username, password)- Authenticate user$logout()- End session$is_authenticated()- Check authentication status$get_current_user()- Get current user info$get_user_home()- Get user dashboard data$search_encounters(query, from = 0, size = 10, sort = NULL, sort_order = NULL)- Search encounters$get_encounter(encounter_id)- Get specific encounter$search_individuals(query, from = 0, size = 10, sort = NULL, sort_order = NULL)- Search individuals$get_individual(individual_id)- Get specific individual$filter_current_user()- Query for encounters by the logged-in user
- R >= 4.2.0
- httr2 >= 1.0.0
- R6 >= 2.5.0
RWildbook and pywildbook follow a shared versioning convention to make feature equivalence explicit:
- Major and minor versions are synchronised across both libraries.
RWildbook 1.2.xandpywildbook 1.2.xexpose the same API surface. - Patch versions are independent. Bug fixes, dependency updates, and other library-specific changes do not require a coordinated release.
- Minor bumps are coordinated. When new features are added they land in both libraries together, then both get the minor bump.
This project follows Semantic Versioning.
For issues, questions, and more information:
- Wildbook - The main Wildbook platform
- pywildbook - An Python client with aligned functionality
Built with: