bitculator.com · API documentation · Get an API key
crates.io — bitculator · docs.rs · GitHub
The official Rust SDK for the Bitculator Data API.
Blocking reqwest + serde, generic typed responses, Rust 2021.
Keep your API key server-side. It is a Bearer token with the
data-apiability and is not meant for client-side embedding.
[dependencies]
bitculator = "1"
serde_json = "1" # for the generic Value type used belowuse bitculator::{Client, Params};
use serde_json::Value;
fn main() -> Result<(), bitculator::Error> {
let client = Client::new(std::env::var("BITCULATOR_API_KEY").unwrap());
// Single resource — the { "data": ... } envelope is unwrapped, then decoded
// into whatever type you ask for (here, a generic JSON Value).
let bitcoin: Value = client.coins().get("bitcoin", Params::new())?;
println!("{}", bitcoin["price"]); // "63520.780763913"
// ...or straight into your own struct.
#[derive(serde::Deserialize)]
struct Coin { symbol: String, price: String }
let btc: Coin = client.coins().get("bitcoin", Params::new())?;
println!("{} {}", btc.symbol, btc.price);
Ok(())
}Prices, rates, and supplies come back as strings (e.g. "63520.780763913")
to preserve full precision. Decode them into a String (or a big-decimal type) —
never an f64, which silently loses precision.
Query parameters are built fluently; keys mirror the API's names (snake_case):
let params = Params::new()
.set("per_page", 50)
.set("sort", "-marketcap")
.set("ids", vec!["bitcoin", "ethereum"]); // slices become CSVPaginated methods return Page<T>. Read one page, or auto-paginate:
// One page at a time.
let mut page: Page<Value> = client.exchanges().list(Params::new().set("per_page", 100))?;
loop {
for exchange in &page.data { /* ... */ }
match page.next_page()? {
Some(next) => page = next,
None => break,
}
}
// Or auto-paginate across every page (each item is a Result).
let page: Page<Value> = client.coins().list(Params::new().set("sort", "-marketcap"))?;
for item in page.auto_paging() {
let coin = item?;
println!("{}", coin["symbol"]);
}Everything returns Result<T, bitculator::Error>. HTTP failures are
Error::Api(ApiError) carrying the { error: { code, message, details } } envelope.
match client.coins().list::<Value>(Params::new().set("per_page", 9999)) {
Ok(page) => { /* ... */ }
Err(bitculator::Error::Api(e)) if e.is_validation() => println!("{:?} {:?}", e.code, e.details),
Err(bitculator::Error::Api(e)) if e.is_rate_limited() => println!("retry after {:?}", e.retry_after),
Err(e) => eprintln!("{e}"),
}ApiError predicates: is_auth (401) · is_permission (403) · is_not_found (404) ·
is_validation (422) · is_rate_limited (429) · is_server (5xx). Transport
failures are Error::Timeout and Error::Connection.
Every response carries X-Quota-* headers, surfaced on the client after each call:
client.coins().list::<Value>(Params::new())?;
let q = client.quota(); // Option<Quota> { limit, remaining, reset, raw }let client = bitculator::Client::builder("your-api-key")
.base_url("https://bitculator.com") // default
.timeout(std::time::Duration::from_secs(30)) // default
.max_retries(2) // auto-retry 429/5xx/network; 0 to disable
.build();The client is blocking. Inside an async runtime (tokio, async-std), reqwest's blocking client panics if called directly — hand the call to a blocking thread instead:
let client = bitculator::Client::new(api_key); // Clone is cheap; move a clone in
let btc: serde_json::Value = tokio::task::spawn_blocking(move || {
client.coins().get("bitcoin", bitculator::Params::new())
})
.await??;(With async-std, use async_std::task::spawn_blocking the same way.)
coins · prices · markets · exchanges · wallets · global_market ·
sentiment · indicators · liquidations · conversion · calculators ·
editorial · alarms · webhooks · meta
Full endpoint reference: https://bitculator.com/en/documentation/api/v1.
MIT