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/toolsfor live shape. - 401 — invalid API key. Rotate via the dashboard; don't retry.
- 429 — rate-limited. Honor
Retry-Afterheader 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
toolandargs. OldertoolName/argumentsexamples are legacy. - Use
GET /api/v1/toolsto 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_attimestamp your agent can use to reason about staleness.