Skip to content
Console

Rust SDK

Use the Rust SDK when your client needs strong types, low overhead, or careful runtime control.

The crate name is thalovant.

Install the latest release with cargo add thalovant. Use the current verified version explicitly when you need reproducible builds:

Terminal window

Rust SDK v0.2.20 adds device login through login_with_browser. MQTT TLS uses the operating system’s native trust store; HTTPS and WSS continue to use the SDK’s Rustls transport.

use thalovant::{
BootstrapIdentityOptions, Client, ControlPlane, HubProtocol, RequestOptions,
};
#[tokio::main]
async fn main() -> thalovant::Result<()> {
let mut control = ControlPlane::default();
control.login("[email protected]", "password", None).await?;
let result = control
.create_client_identity_for_hub_id(
"hub-id",
BootstrapIdentityOptions {
name: "rust-demo-client".into(),
preferred_protocols: vec![HubProtocol::Wss, HubProtocol::Https, HubProtocol::Mqtt],
..Default::default()
},
)
.await?;
let client = Client::with_protocol(result.identity.clone(), HubProtocol::Wss)?;
let reply = client
.ask("Tell me a short clean joke.", RequestOptions::default())
.await?;
println!("{}", reply.text);
client.close().await?;
Ok(())
}

ControlPlane::default() uses https://api.thalovant.com.

Accounts with multi-factor authentication enabled must include a TOTP code or a one-time recovery code with the login. A plain login call is rejected with HTTP 401 and code mfa_required. Use login_with_options; the fields are sent only when set. MFA support needs SDK v0.2.19 or newer.

use thalovant::LoginOptions;
control
.login_with_options(
"password",
LoginOptions {
otp_code: Some("123456".into()),
..Default::default()
},
)
.await?;

Use recovery_code: Some(...) instead of otp_code when the authenticator device is unavailable.

Device login asks your browser to approve the sign-in, so CLIs, services, and agents never handle your password. The SDK prints a short code and the address https://dash.thalovant.com/activate, opens that page when it can, and waits while you approve the request in the dashboard with your normal sign-in, including Google sign-in or MFA. On approval the SDK holds a scoped, revocable API token.

Device login needs SDK v0.2.20 or newer. Approving the request needs a paid workspace plan; a free plan gets HTTP 402.

use thalovant::DeviceLoginOptions;
let token = control
.login_with_browser(DeviceLoginOptions {
scopes: vec!["hubs:read".into(), "clients:write".into()],
client_name: Some("my-tool".into()),
..Default::default()
})
.await?;
println!("signed in, token scopes: {}", token["scopes"]);

Manage the resulting token on the dashboard’s API Tokens page.

Pass a stored API token when the process should start authenticated, such as CI jobs and services. Mint one on the API Tokens page or with login_with_browser; the page shows scopes, expiry, and last use, and can revoke the token at any time.

use thalovant::ControlPlane;
let token = std::env::var("THALOVANT_API_TOKEN").expect("THALOVANT_API_TOKEN is set");
let control = ControlPlane::with_access_token(token);
// Ready for authenticated calls, no login step needed.
let hubs = control.list_hubs(Some(50), None, None).await?;
use thalovant::{Client, RequestOptions};
let client = Client::from_config(Some("prod"))?;
let reply = client
.ask("What can this hub do?", RequestOptions::default())
.await?;
println!("{}", reply.text);
client.close().await?;

Raw identity files work too:

use thalovant::{Client, RequestOptions};
let client = Client::from_file("_identity.json")?;
let reply = client
.ask("What can this hub do?", RequestOptions::default())
.await?;
println!("{}", reply.text);
client.close().await?;

Environment variables work too:

use thalovant::Client;
let client = Client::from_env()?;
let identity = result.identity.clone();
println!("{:?}", identity.enabled_protocols());
println!("{:?}", identity.endpoint_for(HubProtocol::Wss));
println!("{:?}", identity.endpoint_for(HubProtocol::Https));
println!("{:?}", identity.endpoint_for(HubProtocol::Mqtt));
for protocol in [HubProtocol::Wss, HubProtocol::Https, HubProtocol::Mqtt] {
if !identity.supports_protocol(protocol) {
continue;
}
if protocol == HubProtocol::Mqtt && identity.mqtt.is_none() {
continue;
}
let client = Client::with_protocol(identity.clone(), protocol)?;
let reply = client
.ask(&format!("Reply over {protocol:?}."), RequestOptions::default())
.await?;
println!("{protocol:?}: {}", reply.text);
client.close().await?;
}

MQTT requires the identity.mqtt broker credentials returned for that client.

For broker details, see MQTT.

use thalovant::{build_client_context, ClientContextOptions, RequestOptions};
let context = build_client_context(None, ClientContextOptions {
user_id: Some("user-42".into()),
user_name: Some("Ada".into()),
auth_provider: Some("oidc".into()),
source: Some("checkout-kiosk".into()),
platform: Some("kiosk".into()),
locale: Some("en-US".into()),
channel: Some("chat".into()),
..Default::default()
});
let reply = client
.ask(
"Show the next instruction.",
RequestOptions {
context: Some(context),
..Default::default()
},
)
.await?;
use std::time::Duration;
use thalovant::RequestOptions;
use tokio::time::timeout;
let mut events = client.transport.subscribe();
client
.send_utterance("Say the current status.", RequestOptions::default())
.await?;
if let Ok(Ok(event)) = timeout(Duration::from_secs(12), events.recv()).await {
println!("{} {}", event.name, event.text());
}

ask

Send one request and receive normalized text, speech, and display items.

conversation

Keep related turns in one session.

send_action

Send a structured action from a device, UI, or service command.

send_code

Send a scanned value, serial number, QR value, or typed code.

For the full method list, see SDK Functions. Use control.get_operation(operation_id) to follow an accepted command; see Operations.

Since SDK v0.2.22 the control plane can create hubs, runtime groups, and skill installs. Browsing the catalog with list_marketplace_skills needs hubs:read and works on any plan. create_hub, create_runtime_group, install_runtime_group_skill, release_hub, and release_runtime_group need hubs:write and a paid plan.

let group = api.create_runtime_group(json!({"name": "kiosks"})).await?;
let hub = api
.create_hub(json!({"name": "joke-garden", "runtime_group_id": group["id"], "spec": {}}), None)
.await?;
let hub = api.get_hub(hub["id"].as_str().unwrap_or_default()).await?;
let etag = hub["etag"].as_str().unwrap_or_default();
api.update_hub(hub["id"].as_str().unwrap_or_default(), json!({"active": false}), etag).await?;

update_hub and delete_hub take the hub’s current etag as a required argument. See Provision Hubs for the full flow, the immutable fields, and the error table.

Symptom Check
Missing access token Call control.login(...) or control.login_with_browser(...) before private API actions, or use ControlPlane::with_access_token(...).
HTTP 401 with code mfa_required Use control.login_with_options(...) with an otp_code or recovery_code.
API access requires a paid plan Upgrade the workspace before provisioning private resources through the API.
Unsupported protocol Enable that protocol on the hub and create a fresh identity.
MQTT fails immediately Confirm the identity has identity.mqtt broker credentials.