iRoute Rust SDK
iroute-sdk is a dependency-free Rust client for the iRoute v1 HTTP and SSE
API. It returns JSON strings and lets applications choose their JSON and HTTPS
stacks. It supports execution, polling, cancellation, approvals, artifacts,
gateway health, observability, and buffered event replay.
Routing, provider selection, validation, workflow retries, and fallback remain server-side.
Status and requirements
- SDK version:
0.1.0-alpha.1 - Rust baseline:
1.97.1 - Edition: 2024
- Crate name:
iroute-sdk - Runtime dependencies: none
Once published, the public alpha is installed from crates.io as a prerelease crate. The source installation below remains available.
Start iRoute
docker compose --file deploy/compose.sqlite.yaml up --build --wait
The default API URL is http://localhost:8080. See the shared
SDK usage guide for source-run and authentication
options.
Install from crates.io
cargo add iroute-sdk@0.1.0-alpha.1
Install from source
Add the SDK checkout to the application's Cargo.toml:
[dependencies]
iroute-sdk = { path = "../iRoute/sdks/rust" }
Add serde_json in the application if typed JSON parsing is desired. The SDK
itself deliberately has no JSON dependency.
Create a client
use iroute_sdk::{ClientOptions, IRouteClient};
use std::env;
let client = IRouteClient::new(
env::var("IROUTE_URL").unwrap_or_else(|_| "http://localhost:8080".into()),
ClientOptions {
token: env::var("IROUTE_TOKEN").ok(),
tenant_id: Some(env::var("IROUTE_TENANT").unwrap_or_else(|_| "demo".into())),
actor_id: Some(env::var("IROUTE_ACTOR").unwrap_or_else(|_| "rust-app".into())),
permission_scopes: Vec::new(),
},
);
In Development the identity options become headers. In JWT mode the server derives identity and scopes from token claims and will not allow headers to elevate them.
HTTP and HTTPS transport
The built-in HttpTransport supports only http:// and is intended for the
local quick start. It uses blocking TcpStream I/O with a 30-second default
read/write timeout. It does not provide TLS.
Production HTTPS applications must inject a transport backed by their preferred
HTTP/TLS library through IRouteClient::with_transport. The transport receives
an IRouteRequest and returns an IRouteResponse; SDK header construction and
problem-detail mapping remain unchanged.
Submit an execution
use std::time::{SystemTime, UNIX_EPOCH};
let suffix = SystemTime::now()
.duration_since(UNIX_EPOCH)?
.as_nanos();
let idempotency_key = format!("rust-email-{suffix}");
let request = format!(
r#"{{
"taskType":"email.draft",
"input":{{"purpose":"Confirm the Rust SDK integration."}},
"idempotencyKey":"{idempotency_key}",
"constraints":{{
"maxOutputTokens":500,
"deadlineMilliseconds":30000,
"minimumQuality":0.8
}}
}}"#
);
let execution_json = client.execute_json(&request, Some(&idempotency_key))?;
println!("{execution_json}");
The body and header idempotency keys must agree. Keep the same key when retrying the same logical request.
Poll until terminal
The SDK returns complete JSON strings. Parse them with the application's chosen
library. With serde_json:
use serde_json::Value;
use std::thread;
use std::time::Duration;
let mut execution: Value = serde_json::from_str(&execution_json)?;
let execution_id = execution["executionId"]
.as_str()
.ok_or("missing executionId")?
.to_owned();
let terminal = ["Succeeded", "Failed", "Cancelled", "TimedOut"];
while !terminal.contains(&execution["status"].as_str().unwrap_or("")) {
thread::sleep(Duration::from_millis(500));
let current = client
.get_json(&execution_id)?
.ok_or("execution is not visible to this tenant")?;
execution = serde_json::from_str(¤t)?;
}
if execution["status"] == "Succeeded" {
println!("{}", execution["outcome"]["output"]);
} else {
eprintln!("{}", execution["error"]);
}
WaitingForApproval is non-terminal.
Read event replay
let mut last_sequence = 0_u64;
for event_json in client.stream_events_json(&execution_id, last_sequence)? {
let event: serde_json::Value = serde_json::from_str(&event_json)?;
last_sequence = event["sequence"].as_u64().unwrap_or(last_sequence);
println!("{} {}", last_sequence, event["type"]);
}
The built-in transport buffers the HTTP response, and stream_events_json
returns Vec<String> after the stream closes. It is suitable for terminal replay
and bounded local use, not an indefinitely open live feed. Applications needing
incremental SSE should provide a streaming HTTPS client. Persist the sequence
for reconnects and ignore unknown event fields.
Cancel an execution
let accepted = client.cancel(&execution_id)?;
if !accepted {
println!("Execution was not found.");
}
Poll or replay events to confirm the durable terminal result.
Approve or deny an action
Read actionId from an approval.required event:
let decision = format!(
r#"{{"actionId":"{action_id}","approved":true,"reason":"Reviewed by an operator"}}"#
);
let result_json = client.submit_approval_json(&execution_id, &decision)?;
The deciding token needs approval:grant and the action's required scope.
Retrieve an artifact
if let Some(artifact_json) = client.get_artifact_json(&artifact_id)? {
println!("{artifact_json}");
}
Not found returns None, including for cross-tenant access.
Health and observability
use std::collections::BTreeMap;
let health_json = client.get_model_gateway_health_json()?;
let query = BTreeMap::from([
("from".into(), "2026-08-01T00:00:00Z".into()),
("to".into(), "2026-08-02T00:00:00Z".into()),
("taskType".into(), "email.draft".into()),
]);
let summary_json = client.get_observability_summary_json(&query)?;
let timeline_json = client.get_execution_timeline_json(&execution_id)?;
Gateway health returns JSON for HTTP 200 and 503. Summary query keys are
from, to, taskType, and policyVersion. Observability is tenant scoped,
and reported cost has no currency in v1.
Errors and timeouts
use iroute_sdk::IRouteError;
match client.execute_json(&request, Some(&idempotency_key)) {
Ok(json) => println!("{json}"),
Err(IRouteError::Api(error)) => {
eprintln!("HTTP {} {:?}: {:?}", error.status, error.code, error.detail);
eprintln!("{}", error.response_body);
}
Err(IRouteError::Transport(message)) => eprintln!("transport: {message}"),
}
To change the built-in transport timeout, construct
HttpTransport::new(Duration) and pass it to with_transport. The SDK does not
retry automatically.
Method reference
| Method | Result |
|---|---|
execute_json(request, idempotency_key) | Execution JSON string |
get_json(execution_id) | Optional execution JSON |
cancel(execution_id) | false when not found |
submit_approval_json(execution_id, decision) | Approval result JSON |
stream_events_json(execution_id, after_sequence) | Buffered event JSON vector |
get_artifact_json(artifact_id) | Optional artifact JSON |
get_model_gateway_health_json() | Health JSON |
get_observability_summary_json(query) | Summary JSON |
get_execution_timeline_json(execution_id) | Optional timeline JSON |
Run the example and tests
From the repository root:
cargo run --manifest-path sdks/rust/Cargo.toml --example execute
cargo test --manifest-path sdks/rust/Cargo.toml
The checked example is examples/execute.rs. Public wire shapes are defined by the OpenAPI document and JSON Schemas.