Metadata-Version: 2.4
Name: switchy-sdk
Version: 0.3.0
Summary: Official Python SDK for Switchy — the shared AI workspace for small teams.
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) — the shared AI workspace for small teams.

## Install

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

## Hello world (sync)

```python
from switchy import Switchy

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

spaces = client.spaces.list()
space = spaces.projects[0]

client.memory.create(
    content="Launching the new pricing page on May 15.",
    visibility="SPACE",
    space_id=space.id,
)

hits = client.memory.search(query="when do we launch?")
print(hits[0].memory.content)
```

## Hello world (async)

```python
import asyncio
from switchy import AsyncSwitchy

async def main():
    async with AsyncSwitchy(api_key="sk_live_...") as client:
        spaces = await client.spaces.list()
        await client.messages.send(
            spaces.projects[0].slug,
            "sess_123",
            body="@claude what should we ship first?",
        )

asyncio.run(main())
```

## What you can do

The SDK mirrors the v2 REST API one-to-one. Both `Switchy` (sync) and `AsyncSwitchy` (async) expose the same namespaces; methods either return models or raise typed errors.

```python
client.spaces.list(page=1, limit=20)
client.spaces.get(space_id)
client.spaces.create(name=..., description=..., tags=..., idempotency_key=...)

client.sessions.list(space_slug)
client.sessions.create(space_slug, name=..., participant_user_ids=..., default_agent=...)
client.sessions.get(space_slug, session_id)
client.sessions.update(space_slug, session_id, title=..., model=...)
client.sessions.archive(space_slug, session_id)

client.messages.list(space_slug, session_id, before=..., limit=...)
client.messages.send(space_slug, session_id, body=..., parent_message_id=...)
client.messages.react(space_slug, message_id, "👍")

client.memory.list(visibility=..., space_id=..., limit=...)
client.memory.create(content=..., visibility=..., space_id=..., tags=..., source_url=...)
client.memory.search(query=..., space_id=..., limit=...)
client.memory.delete(memory_id)

client.members.list(org_id)
client.members.update_role(org_id, user_id, "ADMIN")
client.members.remove(org_id, user_id)

client.invitations.list(org_id)
client.invitations.create(org_id, email=..., role="MEMBER")
client.invitations.accept(token)

client.billing.credits()
client.billing.checkout(plan="team", interval="annual")
client.billing.portal(return_url=...)
client.billing.topup(pack="medium")  # or topup(credits=1000)

client.mcp.list()
client.mcp.create(display_name=..., mention_slug=..., endpoint_url=..., auth_type=..., secret=...)
client.mcp.delete(server_id)
client.mcp.test(server_id)

client.keys.list()
client.keys.create(name=..., scopes=[...])
```

All return values are pydantic v2 models (`Space`, `Session`, `Message`, `Memory`, etc.) defined in `switchy.models`.

## MCP client

Switchy is also an MCP server. From any MCP-capable client (Claude Desktop, Cursor, your own code) you can call Switchy's tools over JSON-RPC. Sync + async variants:

```python
from switchy import McpClient, AsyncMcpClient

mcp = McpClient(api_key="sk_live_...")
result = mcp.search_memory(query="launch readiness")

# act on behalf of a specific human (required for PRIVATE memory + post_message)
as_alice = mcp.act_as_user("user_abc")
as_alice.post_message(session_id="sess_123", content="Just shipped the migration guide.")
```

See [`/docs/mcp`](https://switchy.build/docs/mcp) for the full tool list.

## Errors

Every method either returns a model or raises a typed error you can catch with `except`:

```python
from switchy import Switchy, ValidationError, ForbiddenError, RateLimitError

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

try:
    client.spaces.create(name="")
except ValidationError as e:
    print(e.issues)  # zod issue list, e.g. [{"path": ["name"], "message": "required"}]
except ForbiddenError:
    print("not allowed")
except RateLimitError as e:
    print(f"retry in {e.retry_after}s")
```

The full error table — codes, HTTP status, when each fires — is at [/docs/api/overview](https://switchy.build/docs/api/overview).

## Idempotency

Every create method accepts `idempotency_key`. If you retry the same call with the same key within 24 hours, the server replays the original response instead of creating a duplicate.

```python
import uuid
key = str(uuid.uuid4())
client.messages.send(slug, sess_id, body="hi", idempotency_key=key)
```

## Migrating from v0.2

v0.3 is the team-workspace SDK. The shape changed because the product changed.

| v0.2 | v0.3 |
|------|------|
| `client.chat.complete(model=..., message=...)` | `client.messages.send(slug, sess_id, body="@model message")` |
| `client.chat.stream(...)` | Use realtime websocket via Ably (planned in 0.4) for live AGENT replies. |
| `client.namespaces.create(name=...)` | `client.spaces.create(name=...)` |
| `client.namespaces.list()` | `client.spaces.list().projects` |
| `client.memory.create_frame(ns, ...)` | `client.memory.create(content=..., visibility=..., space_id=...)` |
| `client.memory.search(query=..., namespaces=[...])` | `client.memory.search(query=..., space_id=...)` |
| `client.sessions.create(namespace=...)` | `client.sessions.create(space_slug, name=...)` |
| `client.knowledge_graph.*` | Removed. KG is internal in v2; use semantic memory search. |
| `client.videos.*` | Removed. Re-shipping under `/v1/videos` later. |

Big-picture changes:

- **Org context is implicit.** Mint a key inside the org you want to talk to; the SDK doesn't need an `org_id` arg on most calls.
- **Visibility is required on memory writes.** `"PRIVATE"` (only you), `"SPACE"` (Space members), `"ORG"` (everyone in the org).
- **The base URL changed** from `…/api/v1` to `…/api`. The SDK defaults; pass `base_url` if you target staging.
- **Errors are typed.** The legacy `RateLimitError` still works (now also exposes the v2 `request_id`); other exceptions split into `AuthError`, `ForbiddenError`, `NotFoundError`, `ValidationError`, `ConflictError`, `ServerError`.
- **Models are pydantic v2.** v0.2 returned dicts; v0.3 returns typed models. If you need a dict, call `.model_dump()`.
- **Async surface is full parity.** `AsyncSwitchy` exposes the same namespaces and methods, just `await`-able.

If you minted a key under one org and used it in another org by accident in v0.2 — that's no longer possible. Keys lock to their minting org.

## Configuration reference

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

## License

MIT
