SDKs
Official client libraries for MUXI
Native SDKs for 12 languages. Build custom UIs, manage user credentials, handle observability events, and control formations programmatically.
Why Use the SDKs?
The SDKs are how developers build products on top of MUXI:
| Use Case | What SDKs Enable |
|---|---|
| Custom Chat UIs | Build your own chat interface instead of using the default |
| Credential Management | Let users add API keys/tokens via your UI (not via chat) |
| Observability Dashboards | Subscribe to 350+ event types for monitoring and debugging |
| User Management | Manage scheduled tasks, memory, and sessions per user |
| Formation Control | Deploy, start, stop, restart formations programmatically |
Security note: When user credentials are required for tools (GitHub, Gmail, etc.), developers can configure whether users provide them via chat or via a dedicated credentials page. The SDK lets you build that credentials page.
Quick Install
Python
Works in scripts, servers, notebooks, and serverless. Python 3.10+
pip install muxi-sdk
Learn more & install ›
TypeScript
Works on your server (Node.js/Bun), in your browsers, and edge.
npm install @muxi/sdk
Learn more & install ›
Go
Works in services, CLIs, serverless, and embedded systems.
go get github.com/muxi-ai/muxi-go
Learn more & install ›
Quick Start
from muxi import FormationClient
client = FormationClient(
server_url="http://localhost:7890",
formation_id="my-assistant",
client_key="your_client_key",
)
for event in client.chat_stream({"message": "Hello!"}):
if event.get("type") == "text":
print(event.get("text"), end="")
import { FormationClient } from "@muxi-ai/muxi-typescript";
const client = new FormationClient({
serverUrl: "http://localhost:7890",
formationId: "my-assistant",
clientKey: "your_client_key",
});
for await (const event of client.chatStream(
{ message: "Hello!" },
"user_123"
)) {
if (typeof event.token === "string") process.stdout.write(event.token);
}
client := muxi.NewFormationClient(&muxi.FormationConfig{
ServerURL: "http://localhost:7890",
FormationID: "my-assistant",
ClientKey: "your_client_key",
})
stream, _ := client.ChatStream(ctx, &muxi.ChatRequest{Message: "Hello!"})
for chunk := range stream {
if token, ok := chunk.Raw["token"].(string); ok {
fmt.Print(token)
}
}
require 'json'
require 'muxi'
client = Muxi::FormationClient.new(
server_url: 'http://localhost:7890',
formation_id: 'my-assistant',
client_key: 'your_client_key'
)
client.chat_stream({ message: 'Hello!' }, user_id: 'user_123') do |event|
next unless event['event'] == 'message'
data = JSON.parse(event['data'])
print data['token'] if data['token'].is_a?(String)
end
FormationClient client = new FormationClient(
"http://localhost:7890",
"my-assistant",
"your_client_key",
null
);
JsonObject request = new JsonObject();
request.addProperty("message", "Hello!");
client.chatStream(request, "user_123", event -> {
if ("message".equals(event.event())) {
JsonObject data = JsonParser.parseString(event.data()).getAsJsonObject();
if (data.has("token")) System.out.print(data.get("token").getAsString());
}
});
val client = FormationClient(FormationConfig(
serverUrl = "http://localhost:7890",
formationId = "my-assistant",
clientKey = "your_client_key"
))
client.chatStream(
mapOf("message" to "Hello!"),
userId = "user_123"
).collect { event ->
if (event.event == "message") {
val token = Json.parseToJsonElement(event.data)
.jsonObject["token"]?.jsonPrimitive?.contentOrNull
if (token != null) print(token)
}
}
let client = try FormationClient(config: FormationConfig(
formationId: "my-assistant",
serverUrl: "http://localhost:7890",
clientKey: "your_client_key"
))
for try await event in await client.chatStream(
["message": "Hello!"],
userId: "user_123"
) {
guard event.event == "message",
let data = event.data.data(using: .utf8),
let payload = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let token = payload["token"] as? String else { continue }
print(token, terminator: "")
}
using System.Text.Json;
var client = new FormationClient(new FormationConfig
{
ServerUrl = "http://localhost:7890",
FormationId = "my-assistant",
ClientKey = "your_client_key"
});
await foreach (var evt in client.ChatStreamAsync(
new Dictionary<string, object> { ["message"] = "Hello!" },
userId: "user_123"))
{
if (evt.Event != "message") continue;
using var document = JsonDocument.Parse(evt.Data);
if (document.RootElement.TryGetProperty("token", out var token) &&
token.ValueKind == JsonValueKind.String)
Console.Write(token.GetString());
}
$client = new FormationClient([
'serverUrl' => 'http://localhost:7890',
'formationId' => 'my-assistant',
'clientKey' => 'your_client_key',
]);
$client->chatStream(
['message' => 'Hello!'],
'user_123',
function (array $event): void {
if ($event['event'] !== 'message') return;
$data = json_decode($event['data'], true);
if (is_string($data['token'] ?? null)) echo $data['token'];
}
);
final client = FormationClient(FormationConfig(
serverUrl: 'http://localhost:7890',
formationId: 'my-assistant',
clientKey: 'your_client_key',
));
await for (final event in client.chatStream(
{'message': 'Hello!'},
userId: 'user_123',
)) {
if (event.event != 'message') continue;
final data = jsonDecode(event.data);
if (data['token'] is String) stdout.write(data['token']);
}
let config = FormationConfig::new(
"http://localhost:7890",
"my-assistant",
"your_client_key",
"",
);
let client = FormationClient::new(config)?;
let stream = client.chat_stream(json!({"message": "Hello!"}), Some("user_123"));
futures::pin_mut!(stream);
while let Some(event) = stream.next().await {
let event = event?;
if event.event == "message" {
let data: serde_json::Value = serde_json::from_str(&event.data)?;
if let Some(token) = data["token"].as_str() { print!("{}", token); }
}
}
muxi::FormationConfig config;
config.server_url = "http://localhost:7890";
config.formation_id = "my-assistant";
config.client_key = "your_client_key";
muxi::FormationClient client(config);
client.chat_stream({{"message", "Hello!"}}, "user_123", [](sdks/const auto& event) {
if (event.event != "message") return;
auto data = nlohmann::json::parse(event.data);
if (data.contains("token") && data["token"].is_string())
std::cout << data["token"].get<std::string>();
});
Local Development
When developing locally with muxi up, use the mode="draft" parameter to route requests through the /draft/ endpoint on your local MUXI server. This allows you to test changes without affecting your live deployment:
client = FormationClient(
server_url="http://localhost:7890",
formation_id="my-assistant",
mode="draft", # Uses /draft/ prefix
client_key="your_client_key",
)
const client = new FormationClient({
serverUrl: "http://localhost:7890",
formationId: "my-assistant",
mode: "draft", // Uses /draft/ prefix
clientKey: "your_client_key",
});
client := muxi.NewFormationClient(&muxi.FormationConfig{
ServerURL: "http://localhost:7890",
FormationID: "my-assistant",
Mode: "draft", // Uses /draft/ prefix
ClientKey: "your_client_key",
})
client = Muxi::FormationClient.new(
server_url: 'http://localhost:7890',
formation_id: 'my-assistant',
mode: 'draft', # Uses /draft/ prefix
client_key: 'your_client_key'
)
FormationClient client = new FormationClient(
"http://localhost:7890",
"my-assistant",
"your_client_key",
null,
30,
"draft"
);
val client = FormationClient(FormationConfig(
serverUrl = "http://localhost:7890",
formationId = "my-assistant",
mode = "draft", // Uses /draft/ prefix
clientKey = "your_client_key"
))
let client = try FormationClient(config: FormationConfig(
formationId: "my-assistant",
serverUrl: "http://localhost:7890",
clientKey: "your_client_key",
mode: "draft"
))
var client = new FormationClient(new FormationConfig
{
ServerUrl = "http://localhost:7890",
FormationId = "my-assistant",
Mode = "draft",
ClientKey = "your_client_key"
});
$client = new FormationClient([
'serverUrl' => 'http://localhost:7890',
'formationId' => 'my-assistant',
'mode' => 'draft',
'clientKey' => 'your_client_key',
]);
final client = FormationClient(
serverUrl: 'http://localhost:7890',
formationId: 'my-assistant',
mode: 'draft', // Uses /draft/ prefix
clientKey: 'your_client_key',
);
let mut config = FormationConfig::new(
"http://localhost:7890",
"my-assistant",
"your_client_key",
"",
);
config.mode = "draft".to_string();
let client = FormationClient::new(config)?;
muxi::FormationConfig config;
config.server_url = "http://localhost:7890";
config.formation_id = "my-assistant";
config.client_key = "your_client_key";
config.mode = "draft";
muxi::FormationClient client(config);
Workflow:
- Run
muxi upto start your formation in draft mode - Use
mode="draft"in your SDK client during development - Run
muxi deployto deploy to production - Remove
mode="draft"(or don't set it) - defaults tomode="live"which uses/api/
The mode parameter only affects URL routing. All other functionality is identical between draft and live modes.
Two Client Types
All SDKs provide two clients:
FormationClient
For interacting with a running formation:
- Chat (streaming & non-streaming)
- Response UI widgets (choices, links, MCP UI resources)
- Sessions & history
- Memory management
- Triggers & scheduled tasks
- Agent configuration
Authentication: Client key (X-Muxi-Client-Key) or Admin key (X-Muxi-Admin-Key)
All SDKs auto-send an X-Muxi-Idempotency-Key. Successful non-streaming
mutations can be replayed safely; streams and failed requests execute again.
Cached responses expose the echoed key on the unwrapped result. See
Idempotency.
Browser usage: The TypeScript SDK's FormationClient works directly in browsers. Use the clientKey for browser apps - it's safe to expose and has limited permissions. Keep the adminKey server-side only.
ServerClient
For managing the MUXI server:
- Deploy formations
- Start/stop/restart formations
- List formations
- Server health & logs
Authentication: HMAC signature (key_id + secret_key)
Common Operations
Chat (Streaming)
for event in formation.chat_stream({"message": "Hello!"}, user_id="user_123"):
if event.get("type") == "text":
print(event.get("text"), end="")
elif event.get("type") == "done":
break
for await (const chunk of formation.chatStream({ message: "Hello!" }, "user_123")) {
if (typeof chunk.token === "string") process.stdout.write(chunk.token);
if (chunk.type === "done") break;
}
stream, errs := client.ChatStream(ctx, &muxi.ChatRequest{Message: "Hello!", UserID: "user_123"})
for chunk := range stream {
if token, ok := chunk.Raw["token"].(string); ok {
fmt.Print(token)
}
}
Memory
# Get memories
memories = formation.get_memories(user_id="user_123")
# Add memory (user_id, mem_type, detail)
formation.add_memory(user_id="user_123", mem_type="preference", detail="User prefers Python")
# Clear buffer
formation.clear_user_buffer(user_id="user_123")
// Get memories
const memories = await formation.getMemories("user_123");
// Add memory (userId, type, detail)
await formation.addMemory("user_123", "preference", "User prefers TypeScript");
// Clear buffer
await formation.clearUserBuffer("user_123");
// Get memories
memories, _ := client.GetMemories(ctx, "user_123")
// Add memory (ctx, userId, type, detail)
client.AddMemory(ctx, "user_123", "preference", "User prefers Go")
// Clear buffer
client.ClearUserBuffer(ctx, "user_123")
Server Management
from muxi import ServerClient
server = ServerClient(
url="http://localhost:7890",
key_id="muxi_pk_...",
secret_key="muxi_sk_...",
)
# Deploy
server.deploy_formation(bundle_path="my-bot.tar.gz")
# List formations
formations = server.list_formations()
# Stop/start/restart
server.stop_formation(formation_id="my-bot")
server.start_formation(formation_id="my-bot")
import { ServerClient } from "@muxi-ai/muxi-typescript";
const server = new ServerClient({
url: "http://localhost:7890",
keyId: "muxi_pk_...",
secretKey: "muxi_sk_...",
});
// Deploy
await server.deployFormation({ bundlePath: "my-bot.tar.gz" });
// List formations
const formations = await server.listFormations();
// Stop/start/restart
await server.stopFormation("my-bot");
await server.startFormation("my-bot");
server := muxi.NewServerClient(&muxi.ServerConfig{
URL: "http://localhost:7890",
KeyID: "muxi_pk_...",
SecretKey: "muxi_sk_...",
})
// Deploy
server.DeployFormation(ctx, &muxi.DeployRequest{BundlePath: "my-bot.tar.gz"})
// List formations
formations, _ := server.ListFormations(ctx)
// Stop/start/restart
server.StopFormation(ctx, "my-bot")
server.StartFormation(ctx, "my-bot")
Multi-Identity Users
Link multiple identifiers (email, Slack ID, etc.) to a single user:
# Resolve the first identifier, then link the others
user = formation.resolve_user("alice@email.com", create_user=True)
result = formation.link_user_identifier(
user["muxi_user_id"],
[
"alice@email.com",
{"identifier": "U12345ABC", "type": "slack"},
["user_123", "internal"],
],
)
print(f"User ID: {result['muxi_user_id']}")
# Now all identifiers resolve to the same user
formation.chat({"message": "Hello"}, user_id="alice@email.com")
formation.chat({"message": "Hello"}, user_id="U12345ABC")
formation.chat({"message": "Hello"}, user_id="user_123")
const user = await formation.resolveUser("alice@email.com", true);
const result = await formation.linkUserIdentifier(
user.muxi_user_id,
[
"alice@email.com",
{ identifier: "U12345ABC", type: "slack" },
]
);
console.log(User ID: ${result.muxi_user_id});
user, _ := client.ResolveUser(ctx, "alice@email.com", true)
result, _ := client.LinkUserIdentifier(
ctx,
user.MuxiUserID,
[]interface{}{
"alice@email.com",
map[string]interface{}{"identifier": "U12345ABC", "type": "slack"},
},
)
fmt.Printf("User ID: %s (%d identifiers)
", user.MuxiUserID, result.Count)
Why? Users interact via multiple channels (Slack, email, web). Multi-identity ensures they get the same memory and context everywhere.
Learn more: Multi-Identity Users →
Session Restore
Restore conversation history from your external storage:
# Restore session from your database
messages = your_db.get_messages(session_id="sess_abc123")
formation.restore_session(
session_id="sess_abc123",
messages=[
{"role": "user", "content": "Hello", "timestamp": "..."},
{"role": "assistant", "content": "Hi!", "timestamp": "..."},
],
user_id="alice@email.com"
)
# User continues with full context
await formation.restoreSession({
sessionId: "sess_abc123",
messages: [
{ role: "user", content: "Hello", timestamp: "..." },
{ role: "assistant", content: "Hi!", timestamp: "..." },
],
userId: "alice@email.com",
});
client.RestoreSession(ctx, &muxi.RestoreRequest{
SessionID: "sess_abc123",
Messages: []muxi.Message{
{Role: "user", Content: "Hello", Timestamp: "..."},
{Role: "assistant", Content: "Hi!", Timestamp: "..."},
},
UserID: "alice@email.com",
})
Why? MUXI's buffer is ephemeral. For persistent chat history (like ChatGPT's sidebar), persist messages yourself and restore when users return.
Error Handling
All SDKs provide typed errors:
| Error | Meaning |
|---|---|
AuthenticationError
| Invalid API key |
AuthorizationError
| Insufficient permissions |
NotFoundError
| Resource doesn't exist |
ValidationError
| Invalid request data |
RateLimitError
| Too many requests |
ServerError
| Server-side error |
ConnectionError
| Network issue |
from muxi import MuxiError, AuthenticationError, RateLimitError
try:
response = formation.chat_stream({"message": "Hello!"}, user_id="user_123")
except AuthenticationError:
print("Invalid API key")
except RateLimitError as e:
print(f"Rate limited, retry after {e.retry_after}s")
except MuxiError as e:
print(f"Error: {e.code} - {e.message}")
import { MuxiError, AuthenticationError, RateLimitError } from "@muxi-ai/muxi-typescript";
try {
await formation.chatStream({ message: "Hello!" }, "user_123");
} catch (err) {
if (err instanceof AuthenticationError) {
console.log("Invalid API key");
} else if (err instanceof RateLimitError) {
console.log(Rate limited, retry after ${err.retryAfter}s);
} else if (err instanceof MuxiError) {
console.log(Error: ${err.code} - ${err.message});
}
}
resp, err := client.Chat(ctx, &muxi.ChatRequest{Message: "Hello!", UserID: "u1"})
if err != nil {
var authErr *muxi.AuthenticationError
var rateLimit *muxi.RateLimitError
switch {
case errors.As(err, &authErr):
log.Println("Invalid API key")
case errors.As(err, &rateLimit):
log.Printf("Rate limited, retry after %d seconds", rateLimit.RetryAfter)
default:
log.Fatal(err)
}
}
Configuration
| Setting | Python | TypeScript | Go | Default |
|---|---|---|---|---|
| Timeout | timeout=30
| timeout: 30000
| 30s built-in | 30s |
| Retries | max_retries=3
| maxRetries: 3
| 3 built-in | 3 |
| Debug | debug=True
| debug: true
| - | Off |
Learn More
- Python SDK →
- TypeScript SDK →
- Go SDK →
- Ruby SDK →
- PHP SDK →
- C# SDK →
- Java SDK →
- Kotlin SDK →
- Swift SDK →
- Dart SDK →
- Rust SDK →
- C++ SDK →
Reference: