Silicon DM Docs
DM / Rust client guide
DM 0.5 is live. Release status

Rust client guide

Current 0.5 guidance: start using DM, sandbox entry, and shared transport / contracts. These replace older manual-pairing and per-profile connection instructions below; the standalone protocol remains compatible.

silicon-dm-client is a stateless, typed client for public Silicon DM operations. Its default HTTP/WebSocket client does not call IAM directly, hold IAM application secrets, persist credentials, or start a daemon. Its protocol types are independent of the backend crate. Enable the optional runtime feature for the durable relay and updater, using a caller-selected private state directory. The stateful dm CLI uses that same SDK runtime.

Installation and configuration

Add silicon-dm-client = "0.4" to your application's Cargo manifest to use the published package. For development against this checkout, depend on crates/client by path.

Client::new accepts a DM origin such as https://backend.dm.teamofsilicons.com, or its /api/v1 base. It appends /api/v1/ for an origin. HTTPS is required except on loopback hosts, where HTTP supports local development. URLs containing credentials, a query or a fragment are rejected. Redirects are disabled. Ordinary HTTP calls time out after 45 seconds. Credentials never appear in a Client debug representation.

use silicon_dm_client::{Client, MessageCreate, PageRequest};
use uuid::Uuid;

let dm = Client::new("https://backend.dm.teamofsilicons.com")?;
// Obtain the short-lived token from the actor, not their password.
let tokens = dm.login(&short_lived_token, &Uuid::new_v4().to_string()).await?;
// Persist tokens privately if your application needs persistence. Configure
// callbacks after login through LocalRuntime, or own the WebSocket directly.
let dm = dm.with_auth(&tokens.access_token, &tokens.organization_id);
let identity = dm.me().await?;
let conversation = dm.create_conversation(
    &[other_actor_public_id], &Uuid::new_v4().to_string()
).await?;
let mut content = MessageCreate::default();
content.text = Some("Hello".into());
content.metadata.insert("task_id".into(), serde_json::json!("task-42"));
let retry_key = Uuid::new_v4().to_string();
let message = dm.send_message(conversation.id, &content, &retry_key).await?;
let history = dm.messages(conversation.id, &PageRequest::default(), false).await?;

The example's short_lived_token and other_actor_public_id come from your application; no token is embedded in documentation. For production deployments, use IAM-issued credentials authorized for the selected organization. Organization and actor authorization remain enforced by the backend. There is no OBO flow.

Authentication and credential lifecycle

login(slt, idempotency_key) sends only {slt} to DM's login endpoint. The backend performs the official IAM client exchange with its own application credentials. The returned Tokens contains access_token, refresh_token, token_type, expires_in, scope, actor, and organization_id. The actor shape is {type, id}. Tokens deliberately do not implement Debug.

with_auth(token, organization_id) builds an authenticated client without disk I/O. refresh(refresh_token, key) returns the replacement token pair; save it atomically before using it. logout(token, key) revokes the supplied token; passing the refresh token revokes its family. Login, refresh and logout require retry-safe idempotency keys. Reuse the same key and identical payload after an uncertain response. Credentials may not authorize every operation: the server returns its actual authorization decision; a client never invents permissions.

me() returns the current actor, organization, principal and session identifiers, organization role and disclosed capabilities. A client can represent a list of actors on a WebSocket only if the backend authorizes every actor.

iam() returns the backend's public IamInfo (app_id, iam_base_url, api_base_url) without an authenticated session. The optional runtime's login_status(profile, test).await verifies and refreshes a persisted login; webhook(profile, test, Some(&url)) configures a callback after login and webhook(profile, test, None) unhooks it. See runtime.

ISI message routing

Set MessageCreate.sender_id = Some("compose@writer:tos".into()) to send with an ISI, or recipient_id = Some("deliberate@cos:tos".into()) to address one. The sender must be the authorized silicon account and the recipient must be a conversation participant. Create conversations using canonical IDs (cos:tos). The returned Message.sender.id stays canonical; Message.content.sender_id and Message.content.recipient_id preserve the routing addresses. They survive history, WebSocket and callback delivery, replies when supplied, bundles and edits. Edits cannot change routing. Use a new idempotency key for a new route. ISI does not change conversation visibility or create another IAM principal; your receiving application dispatches the optional ISI. A socket's actor_id and subscription IDs remain canonical, with the prefixed sender in its message.

Operations

Area Methods Important inputs
Identity login, refresh, logout, me SLT/token and original retry key
Conversations conversations, create_conversation Page request; participant public IDs and retry key
Messages messages, message, send_message, edit_message, delete_message Conversation/message UUIDs, content, observed version, retry key
Receipts record_receipt Delivered/read state and stable device ID
Drafts draft, put_draft, delete_draft Full content; version zero for create or observed version for replacement; versions are retained across deletion
Bundles create_bundle, bundle 1–100 message UUIDs and a display message; Silicon authority
Presence presence; ClientFrame::Presence over a socket Actor public ID; activity or null
GIFs gifs with GifList Trending, search query, or recent
Sandbox management create_test_environment, test_environments, test_environment, update_test_environment, test_environment_key, rotate_test_environment_key, clean_test_environment, delete_test_environment, restore_test_environment Production owner/creator authority and stable mutation keys; clean may use the root key
Realtime connect, connect_with_generation Authorized actor IDs, stable device ID, last known sandbox generation
Local relay relay::RelayClient Local relay URL and its private local bearer
Optional runtime runtime::LocalRuntime::{login,start,start_with,run,client,store} Explicit state directory, local callback, and daemon executable; feature runtime

Every public message and draft includes metadata, a JSON object defaulting to {}. Numbers, booleans, strings, nulls, objects and arrays can be values within that object. The root metadata value must remain an object. Sending, replay, history, drafts, bundle display messages and revisions retain it. A reply sets reply_to_message_id. A metadata-only revision still sends the complete content with edit_message; omitted fields are removed by full replacement.

Attachments use Attachment { permanent_url, name?, content_type?, size? } and are supplied links. Plain URLs inside text need no special treatment. DM does not upload or fetch files. Attachments can be the entire message. Voice includes duration_milliseconds; optional voice_transcript is supplied by the caller. Historical voice rows may contain a null duration. GIFs contain provider_id, url, optional preview_url, and optional title.

Message revisions keep the same message ID and increment the version. Deletion returns a tombstone with deleted_at; update your view to remove its content. Check both ID and version when processing events. Transport deduplication uses the distinct delivery_id, not just message.id.

Errors, pagination and retries

Error::Api preserves status, stable code, human-readable message, full response body, X-Request-ID, and Retry-After when present. On a draft conflict the full body can contain the current server draft. Do not replace that draft silently: read it, merge intentionally, and resubmit its new observed version.

Error::retryable() identifies transport failures, HTTP 408/429 and server failures. It does not retry automatically. For a retry-safe mutation, persist its payload and key before sending, then retry both unchanged. If an operation has only optimistic concurrency, an uncertain successful write may later return a conflict; inspect current server state before deciding what to do. Idempotency does not mean a new key can be substituted after a timeout.

PageRequest accepts cursor and a limit from 1 to 100. Copy next_cursor from one response into the next request. A null cursor ends traversal. Message pages are newest-first. include_bundled_members=true expands original members in history; bundle details separately include the originals.

Test environments

Manage environments with the production login. TestEnvironmentCreate contains name, optional description, IAM test environment ID/key and the imported IAM test application's ID/secret. The DM backend requires test IAM credentials and cannot fall back to production IAM. Creation yields a fresh DM environment and root key; store the root key privately.

let sandbox = Client::new(dm_base)?.with_test_key(dm_test_root_key)?;
let tokens = sandbox.login(&iam_test_slt, &login_key).await?;
let sandbox = sandbox.with_auth(tokens.access_token, tokens.organization_id);
let page = sandbox.conversations(&PageRequest::default()).await?;

The key is carried in X-Testing-Environment-Key on every selected HTTP request and WebSocket upgrade. A key does not turn a production actor into a test actor: the selected IAM sandbox still authenticates the actor. Use without_test() with a production-authenticated client for management. Clean, rotate and restore change the environment generation. See realtime for cursor reset requirements and the test guide for lifecycle rules.

Updates

check_update() reads the latest published stable client version from crates.io and returns UpdateInfo. It stores no timestamp and changes no application files. The optional runtime also provides UpdatePolicy (enabled by default, one check per hour) and updates::after_command to update the SDK dependency and rebuild an explicitly selected Cargo application after its command finishes. Callers own and may persist the policy; setting enabled=false opts out. A linked library cannot replace code already running, so successful rebuilds report that application restart is required. The CLI uses the shared runtime's separate installed-executable update path. See runtime and updates.

Team of Silicons · Edit this page

Usage guides first. Protocol details when you need them.