A Rust client library for the Mindat API.
Mindat is the world's largest open database of minerals, rocks, meteorites, and the localities where they come from. This crate provides a type-safe, async interface to access mineralogical data.
- Full coverage of the Mindat API endpoints
- Strongly-typed request builders and response models
- Async/await support using tokio
- Pagination helpers
- Comprehensive error handling
- Optional GUI application built with Tauri (supports desktop and mobile)
Add this to your Cargo.toml:
[dependencies]
mindat-rs = "0.1"
tokio = { version = "1", features = ["full"] }use mindat_rs::{MindatClient, GeomaterialsQuery, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Create a client with your API token
let client = MindatClient::new("your-api-token");
// Search for quartz
let query = GeomaterialsQuery::new()
.name("quartz")
.ima_approved(true);
let minerals = client.geomaterials(query).await?;
for mineral in minerals.results {
println!("{}: {:?}", mineral.id, mineral.name);
}
Ok(())
}Most API endpoints require authentication with a Mindat API token. You can obtain a token from your Mindat account settings.
Some endpoints (like minerals_ima) can be accessed without authentication:
use mindat_rs::{MindatClient, ImaMineralsQuery};
let client = MindatClient::anonymous();
let minerals = client.minerals_ima(ImaMineralsQuery::new()).await?;use mindat_rs::{MindatClient, GeomaterialsQuery};
let client = MindatClient::new("your-token");
// Find minerals containing copper and sulfur
let query = GeomaterialsQuery::new()
.with_elements("Cu,S")
.ima_approved(true)
.page_size(50);
let minerals = client.geomaterials(query).await?;use mindat_rs::{MindatClient, GeomaterialsQuery, CrystalSystem};
let client = MindatClient::new("your-token");
// Find hard, dense minerals in the cubic system
let query = GeomaterialsQuery::new()
.crystal_systems(vec![CrystalSystem::Isometric])
.hardness_range(7.0, 10.0)
.density_range(5.0, 20.0);
let minerals = client.geomaterials(query).await?;use mindat_rs::{MindatClient, LocalitiesQuery};
let client = MindatClient::new("your-token");
// Find gold localities in Brazil
let query = LocalitiesQuery::new()
.country("Brazil")
.with_elements("Au");
let localities = client.localities(query).await?;Mindat locality names encode the geographic hierarchy (site → district → county → state → country), so a text filter zeroes in on an area. For example, mines and mineral occurrences in the Tucson Mountains, just west of Tucson, Arizona:
use mindat_rs::{MindatClient, LocalitiesQuery};
let client = MindatClient::new("your-token");
let query = LocalitiesQuery::new()
.country("USA")
.name_contains("Tucson Mountains") // Amole Mining District & nearby
.page_size(8);
let localities = client.localities(query).await?;
for loc in localities.results {
println!("{}: {}", loc.id, loc.txt.unwrap_or_default());
}Sample output — copper/lead/silver mines a few miles from downtown, known for wulfenite, vanadinite, chrysocolla, and malachite:
3372: Old Yuma Mine, Tucson Mountain District Saguaro National Park, Amole Mining District, Tucson Mountains, Pima County, Arizona, USA
11361: Gila Monster Mine, Jaynes, Amole Mining District, Tucson Mountains, Pima County, Arizona, USA
20050: Saginaw Hill, Amole Mining District, Tucson Mountains, Pima County, Arizona, USA
21125: Jaynes, Amole Mining District, Tucson Mountains, Pima County, Arizona, USA
34526: Battle Axe Mine, Amole Mining District, Tucson Mountains, Pima County, Arizona, USA
use mindat_rs::{MindatClient, ImaMineralsQuery};
// IMA list doesn't require authentication
let client = MindatClient::anonymous();
let query = ImaMineralsQuery::new()
.search("diamond")
.page_size(10);
let minerals = client.minerals_ima(query).await?;use mindat_rs::{MindatClient, GeomaterialsQuery};
let client = MindatClient::new("your-token");
// Get first page
let query = GeomaterialsQuery::new().page(1).page_size(100);
let page1 = client.geomaterials(query).await?;
println!("Total minerals: {:?}", page1.count);
// Check if there are more pages
if page1.has_next() {
let query = GeomaterialsQuery::new().page(2).page_size(100);
let page2 = client.geomaterials(query).await?;
}use mindat_rs::MindatClient;
use std::time::Duration;
let client = MindatClient::builder()
.token("your-token")
.timeout(Duration::from_secs(60))
.build()?;| Endpoint | Method | Description |
|---|---|---|
geomaterials(query) |
GET | Search minerals with filters |
geomaterial(id) |
GET | Get a specific geomaterial |
geomaterial_varieties(id) |
GET | Get varieties of a geomaterial |
geomaterials_search(q, size) |
GET | Quick search for geomaterials |
localities(query) |
GET | Search localities with filters |
locality(id) |
GET | Get a specific locality |
locality_ages(page) / locality_age(id) |
GET | Locality ages |
locality_statuses(page) / locality_status(id) |
GET | Locality statuses |
locality_types(page) / locality_type(id) |
GET | Locality types |
locality_translations(query) / locality_translation(id) |
GET | Locality name translations |
minerals_ima(query) |
GET | Full search over IMA-approved minerals (no key) |
mineral_ima(id) |
GET | Get a specific IMA mineral |
references(query) / reference(id) |
GET | Bibliographic references |
reference_authors(..) / reference_authors_unique(page) |
GET | Reference authors |
reference_citations(query) / reference_citation(id) |
GET | Reference citations |
reference_types / reference_languages / reference_isbn / reference_ddc / reference_lcc / reference_extra / reference_classifications |
GET | Reference lookup tables |
occurrences(query) / occurrence(id) |
GET | Mineral-at-locality occurrences |
occurrence_statistics(query) / occurrence_statistic(id) |
GET | Aggregated occurrence statistics |
loc_by_min(inc, exc) |
GET | Locality IDs by required/excluded minerals |
crystal_classes(query) / crystal_class(id) |
GET | Crystal classes (point groups) |
space_groups(query) / space_group(id) |
GET | Space groups |
space_group_sets(page) / space_group_set(id) |
GET | Space group sets |
relations(query) / relation(id) |
GET | Relations between minerals |
geoloc_point / geoloc_poly / geomin_point / geomin_poly |
POST | Geospatial point/polygon queries |
localities_within(lat, lon, radius_km) |
— | All locality IDs within a radius (grids the ~10/point cap) |
minerals_within(lat, lon, radius_km) |
— | Minerals near a point → the locality IDs that contain them |
exports() |
GET | Bulk data export links |
dana8_groups() / dana8_subgroups() / dana8(id) |
GET | Dana 8th ed. classification |
strunz10_classes() / strunz10_subclasses() / strunz10_families() / strunz10(id) |
GET | Nickel-Strunz 10th ed. classification |
Note: The upstream
/countries/,/photocount/, and/locgeoregion2/endpoints were removed from the Mindat v1 API. Filter localities by country withLocalitiesQuery::country("USA")instead of a countries list.
Geospatial radius search: the raw
/geoloc-point/and/geomin-point/endpoints return only ~10 localities per point regardless of distance. Thelocalities_withinandminerals_withinhelpers probe a hex grid of points across the radius concurrently and merge the results, so you get full coverage from a single call.
Malformed responses: the API occasionally emits unescaped control characters (e.g. raw newlines in free-text fields like
description_short) which is invalid JSON. Responses are sanitized before parsing so these records no longer fail.
The library provides detailed error types:
use mindat_rs::{MindatClient, MindatError, GeomaterialsQuery};
let client = MindatClient::new("your-token");
let query = GeomaterialsQuery::new().name("quartz");
match client.geomaterials(query).await {
Ok(minerals) => println!("Found {} minerals", minerals.results.len()),
Err(MindatError::AuthenticationRequired) => {
eprintln!("Invalid or missing API token");
}
Err(MindatError::RateLimited) => {
eprintln!("Too many requests, please wait");
}
Err(MindatError::NotFound(msg)) => {
eprintln!("Resource not found: {}", msg);
}
Err(e) => eprintln!("Error: {}", e),
}This crate includes an optional GUI application built with Tauri for testing and exploring the Mindat API. The GUI provides a user-friendly interface to:
- Search minerals by name, elements, or properties
- Browse IMA-approved minerals (no authentication required)
- Explore localities (filter by country name)
- View classification systems (Dana-8, Strunz-10)
- Get detailed mineral information
Linux:
# Ubuntu/Debian
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file \
libssl-dev libayatana-appindicator3-dev librsvg2-dev
# Fedora
sudo dnf install webkit2gtk4.1-devel openssl-devel curl wget file \
libappindicator-gtk3-devel librsvg2-develmacOS:
xcode-select --installWindows:
- Install Microsoft Visual Studio C++ Build Tools
- Install WebView2
# From the repository root
cd gui/src-tauri
cargo build --release
# Or run in development mode
cargo runAfter building, the executable will be at:
- Linux/macOS:
target/release/mindat-gui - Windows:
target\release\mindat-gui.exe
- Launch the application
- Enter your Mindat API token (from mindat.org) and click "Connect"
- Use the sidebar to select different API endpoints
- The "IMA Minerals" endpoint works without authentication
The GUI is built with Tauri 2.0, which supports mobile platforms. To build for iOS:
# Install Tauri CLI
cargo install tauri-cli
# Initialize iOS (requires Xcode)
cd gui/src-tauri
cargo tauri ios init
# Build for iOS
cargo tauri ios buildFor Android:
# Initialize Android (requires Android Studio)
cargo tauri android init
# Build for Android
cargo tauri android buildSee the Tauri Mobile Guide for detailed setup instructions.
- OpenMindat - R package for Mindat API
- mindat_api_test - Python examples
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- Mindat.org for providing the mineralogical database and API
- The OpenMindat R package for implementation reference