---
title: Pilot Rust SDK
description: "Async Rust client for the Cuitty Pilot API — manage playbooks, runs, and providers with reqwest and serde."
section: Pilot
order: 14
updatedAt: 2026-06-01
slug: pilot/sdk/rust
---
# Pilot Rust SDK

`cuitty-pilot-sdk` is an async Rust client for the Pilot API. It uses `reqwest` for HTTP, `serde` for JSON serialization, and `thiserror` for typed errors.

## Install

Add to your `Cargo.toml`:

```toml
[dependencies]
cuitty-pilot-sdk = { path = "../packages/sdk-rust" }
# or when published:
# cuitty-pilot-sdk = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

## Quick start

```rust
use cuitty_pilot_sdk::{PilotClient, StartRunRequest, PilotError};

#[tokio::main]
async fn main() -> Result<(), PilotError> {
    let client = PilotClient::new(
        "http://localhost:4320",
        Some("your-auth-token".into()),
    );

    // List all playbooks
    let playbooks = client.list_playbooks(None).await?;

    // Start a run
    let resp = client.start_run(&StartRunRequest {
        playbook_id: "cloudflare.dns.add-a-record".into(),
        mode: Some("autonomous".into()),
        inputs: Some(serde_json::json!({
            "zone": "example.com",
            "record_name": "app",
            "ip_address": "1.2.3.4"
        })),
        ..Default::default()
    }).await?;

    println!("Run started: {} ({})", resp.id, resp.status);

    // Get run details
    let run = client.get_run(&resp.id).await?;
    println!("Run status: {}", run.status);

    Ok(())
}
```

## PilotClient

The main entry point. All methods are `async` and return `Result<T, PilotError>`.

```rust
// Without auth
let client = PilotClient::new("http://localhost:4320", None);

// With bearer token
let client = PilotClient::new(
    "http://localhost:4320",
    Some("ey...".into()),
);
```

| Method | Returns | Description |
|--------|---------|-------------|
| `health()` | `HealthStatus` | Check server health |
| `list_playbooks(target)` | `Vec<PlaybookSummary>` | List playbooks, optionally filtered |
| `get_playbook(id)` | `Playbook` | Get playbook by ID (latest version) |
| `create_playbook(req)` | `CreatePlaybookResponse` | Create a new playbook |
| `update_playbook(id, req)` | `UpdatePlaybookResponse` | Update (version-bump) a playbook |
| `delete_playbook(id)` | `DeletePlaybookResponse` | Delete all versions |
| `start_run(req)` | `StartRunResponse` | Start a new run |
| `list_runs(status, playbook_id, limit)` | `Vec<Run>` | List runs with optional filters |
| `get_run(id)` | `Run` | Get run details including steps |
| `abort_run(id)` | `AbortRunResponse` | Abort a running or queued run |
| `approve_run(id, req)` | `ApproveResponse` | Submit approval decision for a step |
| `list_providers()` | `Vec<Provider>` | List all provider profiles |

## Playbooks

```rust
// List all
let playbooks = client.list_playbooks(None).await?;

// Filter by target
let cf = client.list_playbooks(Some("cloudflare")).await?;

// Get one
let pb = client.get_playbook("cloudflare.dns.add-a-record").await?;

// Create
let resp = client.create_playbook(&CreatePlaybookRequest {
    target: "cloudflare".into(),
    title: "Add CNAME record".into(),
    yaml: playbook_yaml,
    authored_by: Some("human".into()),
}).await?;

// Update
client.update_playbook("cloudflare.dns.add-cname", &UpdatePlaybookRequest {
    yaml: updated_yaml,
    target: None,
    title: None,
    authored_by: None,
}).await?;

// Delete
client.delete_playbook("cloudflare.dns.add-cname").await?;
```

## Runs

```rust
// Start a run
let resp = client.start_run(&StartRunRequest {
    playbook_id: "cloudflare.dns.add-a-record".into(),
    mode: Some("interactive".into()),
    inputs: Some(serde_json::json!({
        "zone": "example.com",
        "record_name": "api",
        "ip_address": "1.2.3.4"
    })),
    provider_profile_id: Some("cf-prod".into()),
    ..Default::default()
}).await?;

// Get run with steps
let run = client.get_run(&resp.id).await?;
if let Some(steps) = &run.steps {
    for step in steps {
        println!("{}: {} ({}ms)", step.step_id, step.status,
            step.duration_ms.unwrap_or(0));
    }
}

// List recent running runs
let running = client.list_runs(Some("running"), None, Some(10)).await?;

// Abort
client.abort_run(&resp.id).await?;

// Approve a step
client.approve_run(&resp.id, &ApproveRequest {
    step_id: "save".into(),
    decision: Some("approve".into()),
}).await?;
```

## Types

### Core structs

| Struct | Key fields |
|--------|-----------|
| `Playbook` | `id`, `version`, `target`, `title`, `document_yaml`, `authored_by`, `created_at` |
| `PlaybookSummary` | `id`, `version`, `target`, `title`, `authored_by` |
| `Run` | `id`, `project_id`, `playbook_id`, `mode`, `status`, `inputs_json`, `outputs_json`, `steps: Option<Vec<RunStep>>` |
| `RunStep` | `id`, `run_id`, `step_id`, `action`, `status`, `ai_used`, `duration_ms`, `error_message` |
| `Provider` | `id`, `project_id`, `provider`, `label`, `base_url`, `last_authenticated`, `expires_at` |
| `HealthStatus` | `status`, `database`, `driver_pool`, `provider_sessions`, `last_checked` |

### Request structs

| Struct | Fields |
|--------|--------|
| `StartRunRequest` | `playbook_id`, `version?`, `mode?`, `inputs?` (`serde_json::Value`), `provider_profile_id?`, `project_id?` |
| `CreatePlaybookRequest` | `target`, `title`, `yaml`, `authored_by?` |
| `UpdatePlaybookRequest` | `yaml`, `target?`, `title?`, `authored_by?` |
| `ApproveRequest` | `step_id`, `decision?` |

All structs derive `Debug`, `Clone`, `Serialize`, and `Deserialize`.

## Error handling

The SDK uses a single `PilotError` enum with three variants:

```rust
use cuitty_pilot_sdk::PilotError;

match client.start_run(&req).await {
    Ok(resp) => println!("Started: {}", resp.id),
    Err(PilotError::Api { status, message }) => {
        eprintln!("API error {}: {}", status, message);
    }
    Err(PilotError::Connection(err)) => {
        eprintln!("Network error: {}", err);
    }
    Err(PilotError::Other(msg)) => {
        eprintln!("Unexpected: {}", msg);
    }
}
```

| Variant | Fields | When |
|---------|--------|------|
| `Api` | `status: u16`, `message: String` | Non-2xx HTTP response |
| `Connection` | wraps `reqwest::Error` | Network or timeout failure |
| `Other` | `String` | Any other error |

## See also

- [JavaScript SDK](/docs/pilot/sdk/javascript) — TypeScript client with event streaming
- [Playbook reference](/docs/pilot/playbook-reference) — YAML schema for playbook documents
- [Quickstart](/docs/pilot/quickstart) — record and replay your first workflow