HTTP
An inferlet can issue HTTP requests to external services from inside the sandbox. This page covers the request and response shape. Read this after I/O overview.
HTTP is fully supported in the Rust SDK today via the wstd::http crate. The Python and JavaScript SDKs do not yet bind WASI HTTP. The non-Rust tabs below show the intended API shape; the runtime support is in progress. For a working three-language tutorial against this API shape, see Tutorial: build the agent.
Single request
- Rust
- Python
- JavaScript
use inferlet::wstd::http::{Client, Method, Request};
use inferlet::wstd::io::{empty, AsyncRead};
use inferlet::Result;
async fn fetch(url: &str) -> Result<Vec<u8>> {
let client = Client::new();
let req = Request::builder()
.uri(url)
.method(Method::GET)
.body(empty())
.map_err(|e| e.to_string())?;
let resp = client.send(req).await.map_err(|e| e.to_string())?;
let mut buf = Vec::new();
resp.into_body()
.read_to_end(&mut buf)
.await
.map_err(|e| e.to_string())?;
Ok(buf)
}
import requests
def fetch(url: str) -> bytes:
r = requests.get(url, timeout=10)
r.raise_for_status()
return r.content
In an async context, wrap with asyncio.to_thread(requests.get, url) to avoid blocking the event loop.
async function fetchBlob(url: string): Promise<Uint8Array> {
const r = await fetch(url);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return new Uint8Array(await r.arrayBuffer());
}
The Rust path uses WASI HTTP through wstd::http::Client. Connections multiplex automatically: many concurrent requests reuse the underlying transport without you managing it.
Concurrent requests
Issue many requests in parallel with the language's standard async primitive.
- Rust
- Python
- JavaScript
use futures::future;
let urls = vec!["https://a.example/", "https://b.example/", "https://c.example/"];
let results = future::join_all(urls.iter().map(|u| fetch(u))).await;
import asyncio
async def fetch_async(url: str) -> bytes:
return await asyncio.to_thread(requests.get, url, timeout=10)
urls = ["https://a.example/", "https://b.example/", "https://c.example/"]
results = await asyncio.gather(*(fetch_async(u) for u in urls))
const urls = ['https://a.example/', 'https://b.example/', 'https://c.example/'];
const results = await Promise.all(urls.map(u => fetchBlob(u)));
Concurrent fetches share the inferlet's event loop. The model's forward passes do not stall on HTTP: while requests are in flight, g.next()? continues to schedule passes.
JSON request and response
A common pattern: POST a JSON body, read JSON back.
- Rust
- Python
- JavaScript
use inferlet::wstd::http::{body::IntoBody, Client, Method, Request};
use inferlet::wstd::io::AsyncRead;
async fn post_json(url: &str, body: &serde_json::Value) -> Result<serde_json::Value> {
let client = Client::new();
let req = Request::builder()
.uri(url)
.method(Method::POST)
.header("content-type", "application/json")
.body(body.to_string().into_body())
.map_err(|e| e.to_string())?;
let resp = client.send(req).await.map_err(|e| e.to_string())?;
let mut buf = Vec::new();
resp.into_body()
.read_to_end(&mut buf)
.await
.map_err(|e| e.to_string())?;
serde_json::from_slice(&buf).map_err(|e| e.to_string())
}
import requests
def post_json(url: str, body: dict) -> dict:
r = requests.post(url, json=body, timeout=10)
r.raise_for_status()
return r.json()
async function postJson(url: string, body: unknown): Promise<unknown> {
const r = await fetch(url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
Streaming response bodies
For server-sent events or chunked responses, read the body incrementally:
use inferlet::wstd::http::{Client, Method, Request};
use inferlet::wstd::io::{empty, AsyncRead};
let client = Client::new();
let req = Request::builder()
.uri("https://example.com/stream")
.method(Method::GET)
.body(empty())
.map_err(|e| e.to_string())?;
let resp = client.send(req).await.map_err(|e| e.to_string())?;
let mut body = resp.into_body();
let mut chunk = vec![0u8; 4096];
loop {
let n = body.read(&mut chunk).await.map_err(|e| e.to_string())?;
if n == 0 { break; }
process_chunk(&chunk[..n]);
}
The http-server inferlet shows the inverse pattern: an inferlet that serves HTTP, including SSE streaming.
Sandbox boundary
HTTP requests leave the inferlet's sandbox and reach the network through the engine. The engine does not currently enforce per-host allowlists, but the policy may tighten in future releases. Treat outbound HTTP as a privileged operation when you are deciding what an untrusted inferlet should do.
Common services
Examples of services that work well from inside an inferlet:
- Wikipedia REST API (
https://en.wikipedia.org/api/rest_v1/). No auth, free, ideal for retrieval examples. - Search APIs (Bing, Google CSE, Brave). Bring your own key.
- Vector store endpoints (Pinecone, Qdrant, Weaviate). Authenticate with the API key from
runtime::env(...)(when supported) or pass it as inferlet input. - Internal HTTP services for tool execution, code sandboxes, or document parsers.
Next
- Filesystem: persistent local storage.
- MCP: structured tool servers as an alternative to raw HTTP.
- Tutorial: build a parallel research agent: parallel HTTP fetches as a worked example.