bitculator 1.1.0

Official Rust SDK for the Bitculator Data API
Documentation
# Bitculator Data API — Rust SDK

[bitculator.com](https://bitculator.com) · [API documentation](https://bitculator.com/en/documentation/api/v1) · [Get an API key](https://bitculator.com/user/developer/api)
[crates.io — `bitculator`](https://crates.io/crates/bitculator) · [docs.rs](https://docs.rs/bitculator) · [GitHub](https://github.com/Bitculator/bitculator-rust-sdk)

The official Rust SDK for the [Bitculator Data API](https://bitculator.com).
Blocking `reqwest` + `serde`, generic typed responses, Rust 2021.

> Keep your API key server-side. It is a Bearer token with the `data-api` ability
> and is not meant for client-side embedding.

## Install

```toml
[dependencies]
bitculator = "1"
serde_json = "1" # for the generic Value type used below
```

## Quick start

```rust
use 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 are decimal strings

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.

## Parameters

Query parameters are built fluently; keys mirror the API's names (snake_case):

```rust
let params = Params::new()
    .set("per_page", 50)
    .set("sort", "-marketcap")
    .set("ids", vec!["bitcoin", "ethereum"]); // slices become CSV
```

## Pagination

Paginated methods return `Page<T>`. Read one page, or auto-paginate:

```rust
// 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"]);
}
```

## Errors

Everything returns `Result<T, bitculator::Error>`. HTTP failures are
`Error::Api(ApiError)` carrying the `{ error: { code, message, details } }` envelope.

```rust
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`.

## Quota

Every response carries `X-Quota-*` headers, surfaced on the client after each call:

```rust
client.coins().list::<Value>(Params::new())?;
let q = client.quota(); // Option<Quota> { limit, remaining, reset, raw }
```

## Configuration

```rust
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();
```

## Using from async code

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:

```rust
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.)

## Resources

`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>.

## License

MIT