Metadata-Version: 2.5
Name: switchy-sdk
Version: 0.4.0
Summary: Official Python SDK for Switchy: the v1 memory API and the MCP server, authenticated with an org API key.
Project-URL: Homepage, https://switchy.build
Project-URL: Documentation, https://switchy.build/docs
Project-URL: Repository, https://github.com/Switchy-AI/switchy
Project-URL: Bug Tracker, https://github.com/Switchy-AI/switchy/issues
Author-email: Switchy AI <contact@switchy.build>
License: MIT
Keywords: ai,mcp,memory,sdk,switchy,team,workspace
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# switchy-sdk

Official Python SDK for [Switchy](https://switchy.build). `Switchy` / `AsyncSwitchy` are clients for the Switchy **v1 API**, which gives you team memory over HTTP and authenticates with an org API key (`sk_live_…`). `McpClient` / `AsyncMcpClient` are clients for Switchy's **MCP server** and authenticate with an MCP key.

> **Upgrading from 0.3.x?** 0.3.x called Switchy's session-only web-app API, so with an API key every resource call raised `AuthError` (401). 0.4.0 targets the v1 API. See [Migrating from 0.3](#migrating-from-03).

## Install

```bash
pip install switchy-sdk
```

Python 3.9+.

## Get an API key

An owner or admin of your org mints an `sk_live_…` key in **Settings → API keys**. The key is shown once. It is scoped to that org and acts as the user who minted it. (`McpClient` uses a different key; see [MCP client](#mcp-client).)

## Hello world (sync)

```python
from switchy import Switchy

client = Switchy(api_key="sk_live_...")

memory = client.memory.create(
    content="Deploys go out on Thursdays.",
    type="FACT",
    visibility="ORG",
)

hits = client.memory.search(query="deploys")
print(hits[0].content, hits[0].relevance)

page = client.memory.list(limit=20)
print(page.total, [m.content for m in page.memories])

client.memory.delete(memory.id)
```

## Hello world (async)

```python
import asyncio
from switchy import AsyncSwitchy

async def main():
    async with AsyncSwitchy(api_key="sk_live_...") as client:
        hits = await client.memory.search(query="deploys")
        print([h.content for h in hits])

asyncio.run(main())
```

## What you can do

`Switchy` and `AsyncSwitchy` expose the same methods. The async ones are awaitable.

```python
client.memory.list(page=..., limit=..., type=..., min_importance=...)                 # GET    /memory        -> MemoryPage
client.memory.create(content=..., type=..., visibility=..., project_id=..., tags=...)  # POST   /memory        -> CreatedMemory
client.memory.search(query=..., type=..., limit=..., min_importance=...)              # POST   /memory/search -> List[MemorySearchHit]
client.memory.delete(memory_id)                                                       # DELETE /memory?id=    -> DeletedMemory
```

- **Visibility**: `"PRIVATE"` (only you, and the default), `"PROJECT"` (members of one Project, which needs `project_id`), or `"ORG"` (everyone in the org). Reads only return what the key's user is allowed to see.
- **`type`** is required on create: `"FACT"`, `"CONTEXT"`, `"INSTRUCTION"`, `"PREFERENCE"`, `"CONVERSATION"`, `"SUMMARY"` or `"INSIGHT"`.
- **`search`** returns memories whose content contains `query`, case-insensitively, best match first. It is a text match, not a semantic search.
- **`list`** is ordered by importance, then newest first. The server ranks at most 100 visible memories, so `total`, `stats` and paging stop there.
- **Duplicates**: writing content that already exists in the org raises `ConflictError` with `code == "DUPLICATE_CONTENT"`, and `details["id"]` holds the existing memory's id.

Return values are pydantic v2 models from `switchy.models`: `Memory`, `MemoryPage`, `MemoryStats`, `CreatedMemory`, `MemorySearchHit`, `DeletedMemory`. Attributes are snake_case (`created_at`, `has_next_page`, `project_id`). Call `.model_dump()` if you need a dict.

For other v1 endpoints (spec at [`/api/v1/openapi.json`](https://switchy.build/api/v1/openapi.json)), use `request()`. It handles the auth header, the envelope and errors:

```python
data = client.request("GET", "/namespaces")
```

## MCP client

Switchy is also an MCP server. You can call its tools over JSON-RPC from your own code. Sync and async variants are available:

```python
from switchy import McpClient, AsyncMcpClient

mcp = McpClient(api_key="switchy_...")
tools = mcp.tools()
result = mcp.search_memory(query="launch readiness")
```

`McpClient` takes the **site origin** as `base_url` (default `https://switchy.build`) and posts to `{base_url}/mcp/rpc`. Do not pass it the REST base URL (`…/api/v1`).

Tool calls need a key with the matching `mcp:*` scopes. Click **Mint MCP key** in Settings → API keys to get one. An `sk_live_` org key can `initialize()` and list tools, but it has no `mcp:*` scopes, so tool calls raise `McpAuthError` (403, `SCOPE_MISSING`). See [`/docs/mcp`](https://switchy.build/docs/mcp) for the tool list and scopes.

## Errors

Every method returns a model or raises a typed error, chosen by HTTP status. The server's own code is on `err.code`:

| Class | HTTP | Typical `code` |
|-------|------|----------------|
| `ValidationError` | 400 | `VALIDATION_ERROR`, plus `issues` |
| `AuthError` | 401 | `AUTH_ERROR` |
| `ForbiddenError` | 403 | `AUTHORIZATION_ERROR`, `INSUFFICIENT_SCOPE`, `NOT_ORG_MEMBER`, `NOT_PROJECT_MEMBER` |
| `NotFoundError` | 404 | `NOT_FOUND` |
| `ConflictError` | 409 | `DUPLICATE_CONTENT` |
| `RateLimitError` | 429 | `RATE_LIMIT_EXCEEDED`, plus `retry_after` in seconds, `limit`, `reset_at` |
| `ServerError` | 5xx | `INTERNAL_ERROR` |

An `AuthError` message tells you what to check: whether the key was revoked, whether it is an `sk_live_` org key, and whether `base_url` points at the v1 API. It also names the base URL the client used.

```python
from switchy import AuthError, ConflictError, RateLimitError

try:
    client.memory.create(content=content, type="FACT")
except ConflictError as e:
    print("already stored as", e.details["id"])
except RateLimitError as e:
    print(f"retry in {e.retry_after}s")
except AuthError as e:
    print(e)
```

## Rate limits and retries

v1 requests are rate limited per plan, per minute and per day. The SDK retries a `429` up to `max_retries` times (default 2), but only when `Retry-After` is 60 seconds or less. A longer wait, such as a daily cap, raises `RateLimitError` right away so your process doesn't sleep for hours.

## Migrating from 0.3

0.3.x called `https://switchy.build/api`, the API behind the Switchy web app. It authenticates with a browser session, not an API key, so every 0.3.x resource call raised `AuthError`. 0.4.0 calls the v1 API instead.

| 0.3.x | 0.4.0 |
|-------|-------|
| `client.memory.create(content=..., visibility="SPACE", space_id=...)` | `client.memory.create(content=..., type="FACT", visibility="PROJECT", project_id=...)` |
| `client.memory.search(query=..., space_id=...)` → hits with `.memory`, `.score` | `client.memory.search(query=...)` → `MemorySearchHit` with `.content`, `.relevance` |
| `client.memory.list(visibility=..., space_id=..., limit=...)` → `List[Memory]` | `client.memory.list(page=..., limit=..., type=..., min_importance=...)` → `MemoryPage` |
| `client.memory.delete(memory_id)` → `None` | `client.memory.delete(memory_id)` → `DeletedMemory` |
| `client.spaces`, `sessions`, `messages`, `members`, `invitations` | Removed. Use the web app, or `McpClient` (`list_spaces`, `list_sessions`, `get_session_transcript`, `post_message`) |
| `client.billing`, `client.mcp`, `client.keys` | Removed. Manage billing, MCP servers and keys in the web app |
| `idempotency_key=` | Removed. No route implemented it. Duplicate memories are rejected by content |

The full list is in [CHANGELOG.md](./CHANGELOG.md).

## Configuration reference

```python
Switchy(
    api_key="sk_live_...",                     # required
    base_url="https://switchy.build/api/v1",   # default
    timeout=60.0,                              # seconds
    max_retries=2,                             # 429 retries (Retry-After <= 60s)
    client=None,                               # custom httpx.Client (tests / shared pool)
)
```

## License

MIT
