STAC Client for Rust

A friendly, async client for the SpatioTemporal Asset Catalog (STAC) specification, written in Rust. This library helps you interact with STAC APIs to find and retrieve geospatial data.
It is inspired by Python's pystac-client but designed with Rust's principles of type safety, performance, and idiomatic API design in mind.
For architectural decisions and design rationale, see the docs/ directory.
Project Goals
- Idiomatic Rust API: Provide an API that feels natural to Rust developers, with explicit error handling and proper use of ownership.
- Minimal Dependencies: Keep the dependency tree small to ensure fast builds and a lean footprint.
- Well-Documented: Offer clear and comprehensive documentation for all public APIs.
- Thoroughly Tested: Maintain a high standard of testing to guarantee stability and correctness.
Features
- Async First: Built on
tokio for non-blocking I/O.
- Strongly-Typed Models: Rust structs for all major STAC objects (
Catalog, Collection, Item, etc.).
- Powerful Search: A fluent builder pattern for constructing complex search queries.
- Robust Error Handling: Distinct error types for network, parsing, and API-specific issues.
- Customizable Client: Bring your own
reqwest::Client to configure timeouts, proxies, or custom headers.
Optional Features
The following cargo features can be enabled for additional functionality:
pagination: Enables the Client::search_next_page method for easy pagination through search results.
resilience: Provides retry policies with exponential backoff, jitter, and configurable timeouts. Use with ClientBuilder.
auth: Adds pluggable authentication support with ApiKey, BearerToken, and custom AuthLayer implementations.
Installation
Add stac-client to your Cargo.toml:
[dependencies]
stac-client = "0.1.0"
tokio = { version = "1.0", features = ["full"] }
Quick Start
This example shows how to connect to a STAC API, retrieve the root catalog, and run a simple search.
use stac_client::{Client, SearchBuilder};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let api_url = "https://planetarycomputer.microsoft.com/api/stac/v1";
let client = Client::new(api_url)?;
println!("Connected to: {}", client.base_url());
let catalog = client.get_catalog().await?;
println!("Root catalog ID: {}", catalog.id);
let search = SearchBuilder::new()
.collections(vec!["sentinel-2-l2a".to_string()])
.bbox(vec![-74.0, 40.7, -73.9, 40.8]) .datetime("2023-01-01T00:00:00Z/2023-01-31T23:59:59Z")
.limit(10)
.build();
let results = client.search(&search).await?;
println!("Found {} items in the search.", results.features.len());
for item in results.features {
println!(
" - Item '{}' from collection '{}'",
item.id,
item.collection.as_deref().unwrap_or("N/A")
);
for (asset_key, asset) in item.assets {
println!(" - Asset '{}': {}", asset_key, asset.href);
}
}
Ok(())
}
API Reference
Basic Operations
The Client provides methods for fetching core STAC objects:
# use stac_client::Client;
# use std::error::Error;
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn Error>> {
# let client = Client::new("https://example.com")?;
let catalog = client.get_catalog().await?;
let collections = client.get_collections().await?;
let collection = client.get_collection("my-collection-id").await?;
let items = client.get_collection_items("my-collection-id", Some(50)).await?;
let item = client.get_item("my-collection-id", "my-item-id").await?;
let conformance = client.get_conformance().await?;
# Ok(())
# }
Advanced Search
The SearchBuilder allows you to construct detailed queries. The client supports both POST /search (default) and GET /search.
# use stac_client::{Client, SearchBuilder, SortDirection};
# use serde_json::json;
# use std::error::Error;
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn Error>> {
# let client = Client::new("https://example.com")?;
let search = SearchBuilder::new()
.limit(100)
.bbox(vec![-180.0, -90.0, 180.0, 90.0])
.datetime("2024-01-01T00:00:00Z/..") .collections(vec!["collection-a".to_string(), "collection-b".to_string()])
.ids(vec!["item-1".to_string(), "item-2".to_string()])
.intersects(json!({
"type": "Point",
"coordinates": [-122.4, 37.8]
}))
.query("eo:cloud_cover", json!({ "lt": 10 })) .sort_by("datetime", SortDirection::Desc)
.include_fields(vec!["id".to_string(), "properties.datetime".to_string()])
.build();
let results_post = client.search(&search).await?;
let results_get = client.search_get(&search).await?;
# Ok(())
# }
Authentication
The auth feature enables pluggable authentication for protected STAC APIs. Enable it in your Cargo.toml:
[dependencies]
stac-client = { version = "0.1.0", features = ["auth", "resilience"] }
API Key Authentication
use stac_client::{ClientBuilder, auth::ApiKey};
# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientBuilder::new("https://api.example.com/stac")
.auth_layer(ApiKey::new("X-API-Key", "your-secret-key"))
.build()?;
let catalog = client.get_catalog().await?;
# Ok(())
# }
Bearer Token Authentication
use stac_client::{ClientBuilder, auth::BearerToken};
# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientBuilder::new("https://api.example.com/stac")
.auth_layer(BearerToken::new("your-jwt-token"))
.build()?;
let results = client.search(&search_params).await?;
# Ok(())
# }
Multiple Auth Layers
You can chain multiple authentication layers:
use stac_client::{ClientBuilder, auth::{ApiKey, BearerToken}};
# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientBuilder::new("https://api.example.com/stac")
.auth_layer(ApiKey::new("X-API-Key", "key123"))
.auth_layer(BearerToken::new("token456"))
.build()?;
# Ok(())
# }
Custom Authentication
Implement the AuthLayer trait for custom authentication schemes:
use stac_client::auth::AuthLayer;
#[derive(Debug)]
struct CustomAuth {
signature: String,
}
impl AuthLayer for CustomAuth {
fn apply(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
req.header("X-Signature", &self.signature)
}
}
For more examples, see examples/authentication.rs.
Error Handling
The library returns a stac_client::Error for failed operations, allowing you to handle different failure modes.
# use stac_client::{Client, Error};
# #[tokio::main]
# async fn main() {
# let client = Client::new("https://example.com").unwrap();
match client.get_catalog().await {
Ok(catalog) => println!("Success: {}", catalog.id),
Err(Error::Http(e)) => println!("HTTP request failed: {}", e),
Err(Error::Json(e)) => println!("Failed to parse JSON response: {}", e),
Err(Error::Api { status, message }) => {
println!("API returned an error (status {}): {}", status, message)
},
Err(e) => println!("An unexpected error occurred: {}", e),
}
# }
Testing
This project maintains a high level of test coverage. To run the test suite:
cargo test -- --nocapture
Contributing
Contributions are welcome! We have a strict set of development standards to ensure the quality and stability of the crate. Please review:
CONTRIBUTING.md - Contribution guidelines and release process
AGENTS.md - Detailed policies and best practices
All pull requests must pass formatting, linting, and test coverage checks.
License
This project is licensed under either of the following, at your option: