{
  "slug": "pilot/sdk/python",
  "title": "Pilot Python SDK",
  "description": "Async and sync Python clients for the Cuitty Pilot API — manage playbooks, runs, and providers with httpx.",
  "url": "https://cuitty.com/docs/pilot/sdk/python",
  "markdown_url": "https://cuitty.com/docs/pilot/sdk/python.md",
  "json_url": "https://cuitty.com/docs/pilot/sdk/python.json",
  "frontmatter": {
    "title": "Pilot Python SDK",
    "description": "Async and sync Python clients for the Cuitty Pilot API — manage playbooks, runs, and providers with httpx.",
    "order": 16,
    "section": "Pilot",
    "updatedAt": "2026-06-01"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "pilot-python-sdk",
      "text": "Pilot Python SDK"
    },
    {
      "depth": 2,
      "slug": "install",
      "text": "Install"
    },
    {
      "depth": 2,
      "slug": "quick-start",
      "text": "Quick start"
    },
    {
      "depth": 3,
      "slug": "async",
      "text": "Async"
    },
    {
      "depth": 3,
      "slug": "synchronous",
      "text": "Synchronous"
    },
    {
      "depth": 2,
      "slug": "pilotclient-async",
      "text": "PilotClient (async)"
    },
    {
      "depth": 2,
      "slug": "pilotclientsync",
      "text": "PilotClientSync"
    },
    {
      "depth": 2,
      "slug": "playbooks",
      "text": "Playbooks"
    },
    {
      "depth": 2,
      "slug": "runs",
      "text": "Runs"
    },
    {
      "depth": 2,
      "slug": "types",
      "text": "Types"
    },
    {
      "depth": 3,
      "slug": "core-types",
      "text": "Core types"
    },
    {
      "depth": 3,
      "slug": "request-types",
      "text": "Request types"
    },
    {
      "depth": 2,
      "slug": "error-handling",
      "text": "Error handling"
    },
    {
      "depth": 2,
      "slug": "see-also",
      "text": "See also"
    }
  ],
  "body_markdown": "# Pilot Python SDK\n\n`cuitty-pilot` provides both async (`PilotClient`) and synchronous (`PilotClientSync`) Python clients for the Pilot API. Built on `httpx` with dataclass types and context manager support.\n\n## Install\n\n```bash\nsfw pip install cuitty-pilot\n```\n\nRequires Python 3.10+ and `httpx`.\n\n## Quick start\n\n### Async\n\n```python\nimport asyncio\nfrom cuitty_pilot import PilotClient, StartRunRequest\n\nasync def main():\n    async with PilotClient(\"http://localhost:4320\", auth_token=\"pk_...\") as client:\n        # List all playbooks\n        playbooks = await client.list_playbooks()\n\n        # Start a run\n        resp = await client.start_run(StartRunRequest(\n            playbook_id=\"cloudflare.dns.add-a-record\",\n            mode=\"autonomous\",\n            inputs={\"zone\": \"example.com\", \"record_name\": \"app\", \"ip_address\": \"1.2.3.4\"},\n        ))\n        print(f\"Run started: {resp['id']} ({resp['status']})\")\n\n        # Get run details\n        run = await client.get_run(resp[\"id\"])\n        print(f\"Steps: {len(run.steps)}\")\n\nasyncio.run(main())\n```\n\n### Synchronous\n\n```python\nfrom cuitty_pilot import PilotClientSync, StartRunRequest\n\nwith PilotClientSync(\"http://localhost:4320\", auth_token=\"pk_...\") as client:\n    playbooks = client.list_playbooks()\n    resp = client.start_run(StartRunRequest(\n        playbook_id=\"cloudflare.dns.add-a-record\",\n        inputs={\"zone\": \"example.com\", \"record_name\": \"app\", \"ip_address\": \"1.2.3.4\"},\n    ))\n    run = client.get_run(resp[\"id\"])\n    client.close()\n```\n\n## PilotClient (async)\n\nThe primary client. Supports `async with` as a context manager.\n\n```python\nclient = PilotClient(\n    base_url=\"http://localhost:4320\",\n    auth_token=\"ey...\",     # Optional bearer token\n    timeout=30.0,           # Per-request timeout in seconds (default 30.0)\n)\n```\n\n| Method | Returns | Description |\n|--------|---------|-------------|\n| `health()` | `HealthStatus` | Check server health |\n| `list_playbooks(target?)` | `list[PlaybookSummary]` | List playbooks, optionally filtered |\n| `get_playbook(id)` | `Playbook` | Get playbook by ID (latest version) |\n| `create_playbook(req)` | `dict` (`{id, version, created}`) | Create a new playbook |\n| `update_playbook(id, req)` | `dict` (`{id, version}`) | Update (version-bump) a playbook |\n| `delete_playbook(id)` | `dict` (`{deleted}`) | Delete all versions |\n| `start_run(req)` | `dict` (`{id, status}`) | Start a new run |\n| `list_runs(status?, playbook_id?, limit?)` | `list[Run]` | List runs with optional filters |\n| `get_run(id)` | `Run` | Get run details including steps |\n| `abort_run(id)` | `dict` (`{aborted, runId}`) | Abort a running or queued run |\n| `approve_run(id, req)` | `dict` (`{approved, runId, stepId, decision}`) | Submit approval decision |\n| `list_providers()` | `list[Provider]` | List all provider profiles |\n| `close()` | `None` | Close the underlying HTTP client |\n\n## PilotClientSync\n\nSynchronous wrapper with an identical API. Supports `with` as a context manager.\n\n```python\nclient = PilotClientSync(\n    base_url=\"http://localhost:4320\",\n    auth_token=\"ey...\",\n    timeout=30.0,\n)\n```\n\nAll methods are the same as `PilotClient` but without `await`.\n\n## Playbooks\n\n```python\n# List all\nplaybooks = await client.list_playbooks()\n\n# Filter by target\ncf_playbooks = await client.list_playbooks(target=\"cloudflare\")\n\n# Get one\npb = await client.get_playbook(\"cloudflare.dns.add-a-record\")\nprint(pb.title, pb.document_yaml)\n\n# Create\nresp = await client.create_playbook(CreatePlaybookRequest(\n    target=\"cloudflare\",\n    title=\"Add CNAME record\",\n    yaml=playbook_yaml,\n    authored_by=\"human\",\n))\n\n# Update\nawait client.update_playbook(\"cloudflare.dns.add-cname\",\n    UpdatePlaybookRequest(yaml=updated_yaml))\n\n# Delete\nawait client.delete_playbook(\"cloudflare.dns.add-cname\")\n```\n\n## Runs\n\n```python\n# Start a run\nresp = await client.start_run(StartRunRequest(\n    playbook_id=\"cloudflare.dns.add-a-record\",\n    mode=\"interactive\",\n    inputs={\"zone\": \"example.com\", \"record_name\": \"api\", \"ip_address\": \"1.2.3.4\"},\n    provider_profile_id=\"cf-prod\",\n))\n\n# Get run with steps\nrun = await client.get_run(resp[\"id\"])\nfor step in run.steps:\n    print(f\"{step.step_id}: {step.status} ({step.duration_ms}ms)\")\n\n# List runs with filters\nrunning = await client.list_runs(status=\"running\", limit=10)\n\n# Abort\nawait client.abort_run(run_id)\n\n# Approve a step\nawait client.approve_run(run_id, ApproveRequest(\n    step_id=\"save\",\n    decision=\"approve\",\n))\n```\n\n## Types\n\nAll types are Python `dataclass` instances.\n\n### Core types\n\n| Type | Fields |\n|------|--------|\n| `Playbook` | `id`, `version`, `target`, `title`, `document_yaml`, `authored_by`, `created_at` |\n| `PlaybookSummary` | `id`, `version`, `target`, `title`, `authored_by` |\n| `Run` | `id`, `project_id`, `playbook_id`, `playbook_version`, `mode`, `status`, `inputs_json`, `outputs_json`, `provider_profile_id?`, `started_at?`, `completed_at?`, `failed_step_id?`, `error_message?`, `steps: list[RunStep]` |\n| `RunStep` | `id`, `run_id`, `step_id`, `action`, `status`, `selector_tried_json`, `ai_used`, `started_at?`, `completed_at?`, `duration_ms?`, `error_message?` |\n| `Provider` | `id`, `project_id`, `provider`, `label`, `base_url`, `last_authenticated?`, `expires_at?` |\n| `HealthStatus` | `status`, `database`, `driver_pool: dict`, `provider_sessions: dict`, `last_checked` |\n\n### Request types\n\n| Type | Fields |\n|------|--------|\n| `StartRunRequest` | `playbook_id`, `mode=\"autonomous\"`, `version?`, `inputs?: dict`, `provider_profile_id?`, `project_id?` |\n| `CreatePlaybookRequest` | `target`, `title`, `yaml`, `authored_by?` |\n| `UpdatePlaybookRequest` | `yaml`, `target?`, `title?`, `authored_by?` |\n| `ApproveRequest` | `step_id`, `decision=\"approve\"` |\n\n## Error handling\n\nThe SDK raises typed exceptions from a common `PilotError` base class:\n\n```python\nfrom cuitty_pilot import PilotAPIError, PilotConnectionError\n\ntry:\n    await client.start_run(StartRunRequest(playbook_id=\"nonexistent\"))\nexcept PilotAPIError as e:\n    print(f\"API error {e.status}: {e.message}\")\nexcept PilotConnectionError as e:\n    print(f\"Cannot reach Pilot: {e}\")\n    if e.cause:\n        print(f\"Underlying: {e.cause}\")\n```\n\n| Exception | Properties | When |\n|-----------|-----------|------|\n| `PilotError` | base class | All SDK errors |\n| `PilotAPIError` | `status: int`, `message: str` | Non-2xx HTTP response |\n| `PilotConnectionError` | `message: str`, `cause: Exception \\| None` | Network or timeout failure |\n\n## See also\n\n- [JavaScript SDK](/docs/pilot/sdk/javascript) — TypeScript client with event streaming\n- [Playbook reference](/docs/pilot/playbook-reference) — YAML schema for playbook documents\n- [Quickstart](/docs/pilot/quickstart) — record and replay your first workflow",
  "links_out": [
    "/docs/pilot/sdk/javascript",
    "/docs/pilot/playbook-reference",
    "/docs/pilot/quickstart"
  ]
}