Tutorials & SDKs

Rust Integration

Use Hive from async Rust services with reqwest.


Installation

[dependencies]
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Quick Start

use reqwest::Client;
use serde_json::{json, Value};

const BASE_URL: &str = "https://mcp.hiveintelligence.xyz";

async fn execute(api_key: &str, tool: &str, args: Value) -> Result<Value, reqwest::Error> {
    let client = Client::new();

    client
        .post(format!("{BASE_URL}/api/v1/execute"))
        .header("Authorization", format!("Bearer {api_key}"))
        .json(&json!({
            "tool": tool,
            "args": args
        }))
        .send()
        .await?
        .json()
        .await
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("HIVE_API_KEY")?;
    let price = execute(&api_key, "get_price", json!({
        "ids": "bitcoin",
        "vs_currencies": "usd"
    })).await?;

    println!("Bitcoin: {}", price["bitcoin"]["usd"]);
    Ok(())
}

Reusable Client

use reqwest::Client;
use serde_json::{json, Value};

pub struct HiveClient {
    api_key: String,
    base_url: String,
    client: Client,
}

impl HiveClient {
    pub fn new(api_key: String) -> Self {
        Self {
            api_key,
            base_url: "https://mcp.hiveintelligence.xyz".to_string(),
            client: Client::new(),
        }
    }

    pub async fn execute(&self, tool: &str, args: Value) -> Result<Value, reqwest::Error> {
        self.client
            .post(format!("{}/api/v1/execute", self.base_url))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&json!({
                "tool": tool,
                "args": args
            }))
            .send()
            .await?
            .json()
            .await
    }

    pub async fn list_tools(&self, limit: usize) -> Result<Value, reqwest::Error> {
        self.client
            .get(format!("{}/api/v1/tools?limit={limit}", self.base_url))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .send()
            .await?
            .json()
            .await
    }
}

Example Calls

let client = HiveClient::new(std::env::var("HIVE_API_KEY")?);

let market = client.execute("get_coins_market_data", json!({
    "vs_currency": "usd",
    "order": "market_cap_desc",
    "per_page": 5
})).await?;

let wallet = client.execute("moralis_get_wallet_net_worth", json!({
    "address": "0x1234...",
    "chain": "eth"
})).await?;

let security = client.execute("get_token_security", json!({
    "contract_addresses": "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
    "chainId": "1"
})).await?;

Error Handling

Hive returns a JSON envelope on every response. Model the error shape and route retries off HTTP status codes:

use serde::Deserialize;
use std::time::Duration;
use thiserror::Error;
use tokio::time::sleep;

#[derive(Debug, Deserialize)]
pub struct HiveError {
    pub code: String,
    pub message: String,
}

#[derive(Debug, Error)]
pub enum HiveClientError {
    #[error("hive api error ({status}): {code}: {message}")]
    Api { status: u16, code: String, message: String },
    #[error("rate limited; retry after {0}s")]
    RateLimited(u64),
    #[error(transparent)]
    Transport(#[from] reqwest::Error),
    #[error("exhausted retries")]
    ExhaustedRetries,
}

impl HiveClient {
    pub async fn execute_with_retry(
        &self,
        tool: &str,
        args: Value,
        max_retries: u32,
    ) -> Result<Value, HiveClientError> {
        for attempt in 0..max_retries {
            let response = self.client
                .post(format!("{}/api/v1/execute", self.base_url))
                .header("Authorization", format!("Bearer {}", self.api_key))
                .json(&json!({ "tool": tool, "args": args }))
                .send()
                .await?;

            let status = response.status();
            if status == reqwest::StatusCode::TOO_MANY_REQUESTS {
                let retry_after = response
                    .headers()
                    .get("retry-after")
                    .and_then(|v| v.to_str().ok())
                    .and_then(|v| v.parse::<u64>().ok())
                    .unwrap_or(1 << attempt);
                sleep(Duration::from_secs(retry_after)).await;
                continue;
            }
            if status.is_server_error() {
                sleep(Duration::from_secs(1 << attempt)).await;
                continue;
            }
            if status.is_client_error() {
                let err: HiveError = response.json().await?;
                return Err(HiveClientError::Api {
                    status: status.as_u16(),
                    code: err.code,
                    message: err.message,
                });
            }
            return Ok(response.json().await?);
        }
        Err(HiveClientError::ExhaustedRetries)
    }
}

Key HTTP codes to handle:

  • 400 — invalid tool name or args. Inspect GET /api/v1/tools for live shape.
  • 401 — invalid API key. Rotate via the dashboard; don't retry.
  • 429 — rate-limited. Honor Retry-After header before retrying.
  • 502 / 503 — upstream provider failure. Exponential backoff.

Typed Responses

For tools whose shape you know, skip the generic Value and derive into a struct:

#[derive(Debug, Deserialize)]
struct PriceResponse {
    #[serde(flatten)]
    prices: std::collections::HashMap<String, std::collections::HashMap<String, f64>>,
}

let raw = client.execute("get_price", json!({
    "ids": "bitcoin,ethereum",
    "vs_currencies": "usd"
})).await?;

let typed: PriceResponse = serde_json::from_value(raw)?;
let btc = typed.prices["bitcoin"]["usd"];

serde_json::from_value gives you fail-fast parsing; schema drift surfaces at the boundary, not deep in business logic.


Concurrent Fan-Out

For research agents that need multiple tool calls per turn, use tokio::join! or futures::future::join_all:

use futures::future::try_join_all;

async fn market_brief(client: &HiveClient) -> Result<Value, HiveClientError> {
    let calls = vec![
        client.execute("get_price", json!({ "ids": "bitcoin,ethereum", "vs_currencies": "usd" })),
        client.execute("get_protocol_tvl", json!({})),
        client.execute("get_open_interest", json!({ "exchange": "binance" })),
    ];
    let results = try_join_all(calls).await?;
    Ok(json!({
        "prices": results[0],
        "tvl":    results[1],
        "derivatives_oi": results[2],
    }))
}

Hive bills one credit per call regardless of concurrency, so fan-out is the right default for research and reporting loops.


Tool Discovery

Resolve schemas at runtime so new Hive tools show up automatically:

async fn discover(client: &HiveClient, search: Option<&str>) -> Result<Vec<Value>, reqwest::Error> {
    let mut cursor: Option<String> = None;
    let mut items: Vec<Value> = Vec::new();
    loop {
        let mut url = format!("{}/api/v1/tools?limit=200", client.base_url);
        if let Some(c) = &cursor { url.push_str(&format!("&cursor={c}")); }
        if let Some(s) = search { url.push_str(&format!("&search={s}")); }

        let page: Value = client.client
            .get(&url)
            .header("Authorization", format!("Bearer {}", client.api_key))
            .send()
            .await?
            .json()
            .await?;

        if let Some(data) = page["data"].as_array() {
            items.extend(data.iter().cloned());
        }
        match page["meta"]["cursor"].as_str() {
            Some(c) => cursor = Some(c.to_string()),
            None    => return Ok(items),
        }
    }
}

Notes

  • The current REST payload keys are tool and args. Older toolName / arguments examples are legacy.
  • Use GET /api/v1/tools to inspect the live catalog before hard-coding schemas.
  • Failed 4xx calls do not consume credits; 5xx server errors are refunded.
  • Every response carries a fetched_at timestamp your agent can use to reason about staleness.