leash-sdk 0.4.0

Rust SDK for the Leash platform — unified async client for auth, env, and integrations.
Documentation

leash-sdk (Rust)

Rust SDK for the Leash platform — one unified async client for authentication, runtime env vars, and integrations.

Framework-agnostic. Works with axum, actix-web, plain http::Request, and anything else you can extract cookies + headers from.

Installation

[dependencies]
leash-sdk = "0.4"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

# Optional framework integrations — pick what you use.
# leash-sdk = { version = "0.4", features = ["axum"] }
# leash-sdk = { version = "0.4", features = ["actix-web"] }

Quick Start

use leash_sdk::{Leash, GmailListParams};

#[tokio::main]
async fn main() -> leash_sdk::Result<()> {
    // Server-to-server (CLI / agent / cron).
    let leash = Leash::from_api_key(std::env::var("LEASH_API_KEY").unwrap())?;

    // Identity
    let _ = leash.auth().user().await?;

    // Env vars (60s TTL cache, dedicated get_fresh for cache-bypass)
    let openai_key = leash.env().get("OPENAI_API_KEY").await?;

    // Integrations — typed verbs through the platform proxy.
    let msgs = leash.integrations().gmail().list_messages(GmailListParams {
        max_results: Some(5),
        ..Default::default()
    }).await?;

    println!("openai_key set? {}", openai_key.is_some());
    println!("messages: {}", msgs.messages.len());
    Ok(())
}

Request-bound construction

The canonical entry point: Leash::new(req) reads LEASH_API_KEY from env, plus the inbound request's leash-auth cookie and Authorization: Bearer header.

axum

use axum::{routing::get, Router, extract::Request};
use leash_sdk::Leash;

async fn me(req: Request) -> Result<String, String> {
    let leash = Leash::new(&req).map_err(|e| e.to_string())?;
    let user = leash.auth().user().await.map_err(|e| e.to_string())?;
    Ok(format!("hi {}", user.map(|u| u.name).unwrap_or_default()))
}

let _app = Router::<()>::new().route("/me", get(me));

actix-web

use actix_web::{get, HttpRequest, Responder};
use leash_sdk::Leash;

#[get("/me")]
async fn me(req: HttpRequest) -> impl Responder {
    let leash = Leash::new(&req).unwrap();
    let user = leash.auth().user().await.unwrap();
    format!("hi {}", user.map(|u| u.name).unwrap_or_default())
}

Plain http::Request

use leash_sdk::Leash;

let req = http::Request::builder()
    .header("cookie", "leash-auth=…")
    .body(()).unwrap();
let leash = Leash::new(&req).unwrap();

Auth precedence

  1. LEASH_API_KEY env var (or Leash::with_api_key(...)).
  2. Authorization: Bearer <jwt> on the inbound request — used for identity and, only when no API key is configured, as a fallback bearer on env-fetch endpoints. Never forwarded on integration POSTs (the platform's verifyToken() rejects user JWTs before the API-key check runs).
  3. leash-auth cookie — forwarded to the platform on integration calls.

Integration verbs

Provider Verbs
gmail() list_messages, get_message, send_message, search_messages, list_labels, get_profile
calendar() (alias google_calendar()) list_calendars, list_events, create_event, get_event
drive() (alias google_drive()) list_files, get_file, download_file, create_folder, upload_file, delete_file, search_files
linear() list_issues, get_issue, create_issue, update_issue, add_comment, list_teams, list_projects
provider(name) call(action, body) — generic escape hatch for Slack, GitHub, HubSpot, …
use leash_sdk::{Leash, LinearListIssuesFilter};
# async fn ex() -> leash_sdk::Result<()> {
let leash = Leash::from_api_key("lsk_…")?;

let issues = leash.integrations().linear().list_issues(LinearListIssuesFilter {
    state_type: Some("started".into()),
    ..Default::default()
}).await?;

let res = leash.integrations().provider("slack").call(
    "post_message",
    serde_json::json!({ "channel": "#general", "text": "hi" }),
).await?;
# let _ = (issues, res); Ok(()) }

Errors

LeashError is a structured enum. Predicates cover the common branches:

# fn handle(err: leash_sdk::LeashError) {
if err.is_plan_block()          { /* show upgrade UI */ }
if err.is_connection_required() { /* show "connect Gmail" CTA */ }
if err.is_unauthorized()        { /* re-auth */ }
if err.is_key_not_declared()    { /* show developer-facing hint */ }
# }

env().get(key) returns Result<Option<String>>Ok(None) for KEY_NOT_DECLARED, Err(_) for actual errors so you can branch with if value.is_none().

TLS

reqwest is configured with rustls-tls only — no OpenSSL transitively.

What's NOT in 0.4 yet

  • Dev-auth cookie-exchange handler (Leash::createDevAuthHandler in TS) — the local-dev /api/leash/dev-auth flow is TS-only for 0.4. Rust callers using local dev should rely on the cookie pre-set by leash up instead.
  • Browser-mode client — there's no WASM/browser build. Construct from a server route.
  • MCP exec helpers (run_mcp, custom MCP config) — out of scope for the unified 0.4 client. Use the generic provider(name).call(...) escape hatch if you need to reach an MCP-backed integration directly.
  • Connection status / connect URL helpers (is_connected, get_connect_url, get_connections) — surface to be re-added in a follow-up once the platform's /api/integrations/connections shape stabilises.

If you depended on any of these in 0.3.x, pin to leash-sdk = "0.3" and migrate when the replacement lands.

Compatibility

SDK Wire-compatible with platform
0.4.x leash.build (current)
0.3.x leash.build (legacy LeashIntegrations surface)

License

Apache-2.0.