Tutorials & Integrations
Rust Integration
Written by Hive Intelligence, Product docsLast updated
Use Hive from async Rust services with reqwest.
Installation
toml
[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"] }
thiserror = "2"
futures = "0.3"Quick start
rust
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?
.error_for_status()?
.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["data"]["bitcoin"]["usd"]);
println!("Fetched at: {}", price["meta"]["fetched_at"]);
Ok(())
}Reusable client
rust
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?
.error_for_status()?
.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?
.error_for_status()?
.json()
.await
}
}Example calls
rust
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("alchemy_get_token_balances_by_wallet", json!({
"address": "0x1234...",
"network": "eth-mainnet"
})).await?;
let security = client.execute("get_token_security", json!({
"contract_addresses": "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
"chainId": "1"
})).await?;
println!("Market data: {}", market["data"]);
println!("Wallet data: {}", wallet["data"]);
println!("Security data: {}", security["data"]);Error handling
Hive returns a JSON envelope on every response. Model the error shape and route retries off HTTP status codes:
rust
use serde::Deserialize;
use serde_json::{json, Value};
use std::time::Duration;
use thiserror::Error;
use tokio::time::sleep;
#[derive(Debug, Deserialize)]
pub struct HiveErrorEnvelope {
pub error: HiveErrorBody,
}
#[derive(Debug, Deserialize)]
pub struct HiveErrorBody {
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.clone() }))
.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: HiveErrorEnvelope = response.json().await?;
return Err(HiveClientError::Api {
status: status.as_u16(),
code: err.error.code,
message: err.error.message,
});
}
let payload: Value = response.json().await?;
if payload.get("ok").and_then(Value::as_bool) == Some(false) {
let err = payload.get("error").unwrap_or(&Value::Null);
return Err(HiveClientError::Api {
status: status.as_u16(),
code: err
.get("code")
.and_then(Value::as_str)
.unwrap_or("execution_error")
.to_string(),
message: err
.get("message")
.and_then(Value::as_str)
.unwrap_or("Hive execution failed")
.to_string(),
});
}
return Ok(payload);
}
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. - 503 (
PROVIDER_UNAVAILABLE): upstream provider failure. Exponential backoff.
Typed responses
For tools whose shape you know, skip the generic Value and derive into a struct:
rust
#[derive(Debug, Deserialize)]
struct HiveExecuteResponse<T> {
data: T,
meta: Value,
}
#[derive(Debug, Deserialize)]
struct PriceData {
#[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: HiveExecuteResponse<PriceData> = serde_json::from_value(raw)?;
let btc = typed.data.prices["bitcoin"]["usd"];
let fetched_at = typed.meta["fetched_at"].as_str();serde_json::from_value gives you fail-fast parsing, so schema drift surfaces at the boundary before it reaches your business logic.
Concurrent fan-out
For research agents that need multiple tool calls per turn, use tokio::join! or futures::future::join_all:
rust
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!({ "protocol": "aave" })),
client.execute("get_open_interest", json!({ "exchange": "binance", "symbol": "BTC/USDT:USDT" })),
];
let results = try_join_all(calls).await?;
Ok(json!({
"prices": results[0]["data"],
"tvl": results[1]["data"],
"derivatives_oi": results[2]["data"],
}))
}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 your app can inspect current Hive tool definitions:
rust
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. - Authenticated execution and discovery requests can count against quota once accepted by the backend. Cache discovery responses and validate inputs before sending requests.
- Every execution includes a
fetched_attimestamp your agent can use to reason about staleness.
Related
- API Integration - REST endpoint and request envelope
- Response Formats - parse execution metadata and structured errors
- Rate Limits - retry and quota behavior for async services
- SDK Overview - compare Rust REST with other integration paths