> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mesa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

The Mesa Python SDK is the ergonomic async client for Mesa. It wraps the generated `mesa-rest` client, resolves the default organization for you, and exposes a native virtual filesystem for repo I/O and shell execution.

Python 3.10 or newer is required.

## Installation

```bash theme={null}
pip install mesa-sdk
```

## Create a client

```python theme={null}
import asyncio
from mesa_sdk import Mesa

mesa = Mesa(api_key="mk_...")

async def main():
    repos = await mesa.repos.list()
    print(f"found {len(repos.repos)} repos")

asyncio.run(main())
```

<Tip>
  Set `MESA_API_KEY` in your environment to omit `api_key` from the constructor.
</Tip>

## Client options

```python theme={null}
from mesa_sdk import Mesa

mesa = Mesa(
    api_key="mk_...",
    api_url="https://api.mesa.dev/v1",
    vcs_url="https://vcs.mesa.dev",
    org="acme",
    user_agent="my-app/1.0.0",
)
```

<ParamField path="api_key" type="str | None">
  API key used as a Bearer token — the only credential the client accepts. (Access tokens are signed from it internally; you cannot construct a client from one.) If omitted, the SDK reads `MESA_API_KEY` from the environment. Construction raises `MissingCredentialError` if no API key is available.
</ParamField>

<ParamField path="api_url" type="str">
  REST API base URL. Defaults to `https://api.mesa.dev/v1`. `http` and `https` are accepted. Trailing slashes are stripped.
</ParamField>

<ParamField path="vcs_url" type="str | None">
  VCS service URL used by `mesa.fs.mount(...)`. Leave unset for the default Mesa deployment. Set this when using a non-default API deployment with MesaFS.
</ParamField>

<ParamField path="org" type="str | None">
  Default organization slug. When provided, SDK calls skip `/whoami` for organization resolution.
</ParamField>

<ParamField path="user_agent" type="str | None">
  Appended to the SDK user agent. The default user agent starts with `mesa-sdk-python`.
</ParamField>

## Client lifecycle

Create one `Mesa` client for your process or application and reuse it across request handlers. If your framework has a lifespan hook and you want explicit cleanup, wrap the client in `async with Mesa(...)` at application lifespan, not inside each handler.

```python theme={null}
from mesa_sdk import Mesa

mesa = Mesa()

async def handler():
    repos = await mesa.repos.list()
    return repos
```

## Organization resolution

The SDK chooses an organization in this order:

| Source            | When it applies                                                    |
| ----------------- | ------------------------------------------------------------------ |
| Per-call `org`    | The method call passes `org="..."`.                                |
| Constructor `org` | The client was created with `Mesa(org="...")`.                     |
| `mesa.whoami()`   | No per-call or constructor org was supplied. The result is cached. |

```python theme={null}
from mesa_sdk import Mesa

mesa = Mesa(org="acme")

await mesa.repos.list()
await mesa.repos.list(org="other-org")
```

## Resource namespaces

| Namespace              | Purpose                                                                                       |
| ---------------------- | --------------------------------------------------------------------------------------------- |
| `mesa.repos`           | Create, read, update, delete, sync upstreams, and read upstream sync history on repositories. |
| `mesa.content`         | Read files, symlinks, and directory listings without mounting.                                |
| `mesa.changes`         | Create, patch, and inspect Mesa changes.                                                      |
| `mesa.diffs`           | Inspect diffs and conflicts between changes.                                                  |
| `mesa.bookmarks`       | Manage branch-like bookmark refs.                                                             |
| `mesa.api_keys`        | Create, list, and revoke Mesa API keys.                                                       |
| `mesa.tokens`          | Sign short-lived, scoped access tokens locally from your API key.                             |
| `mesa.webhook_targets` | Manage outbound webhook targets.                                                              |
| `mesa.fs`              | Mount repos as a virtual filesystem and run Bash commands.                                    |
| `mesa.org`             | Fetch organization metadata.                                                                  |

## Response objects

The high-level SDK returns model instances generated by `mesa-rest`. Use attribute access, not dictionary access.

```python theme={null}
repos = await mesa.repos.list()
for repo in repos.repos:
    print(repo.name, repo.head_change_id)
```

## Common types

Import common dataclasses and native result types from `mesa_sdk`.

```python theme={null}
import base64
from mesa_sdk import Author, FileDelete, FileUpsert

files = [
    FileUpsert(path="README.md", content=base64.b64encode(b"hello").decode()),
    FileDelete(path="old.txt"),
]
author = Author(name="Alice", email="alice@example.com")
```

| Type                         | Purpose                                                                   |
| ---------------------------- | ------------------------------------------------------------------------- |
| `Author`                     | Commit author or committer identity.                                      |
| `FileUpsert`                 | Create or replace one file in a change. `content` must be base64-encoded. |
| `FileDelete`                 | Delete one file in a change.                                              |
| `WholeFileResolution`        | Resolve a conflicted path by replacing full content or taking one side.   |
| `HunkResolution` / `HunkFix` | Resolve individual conflict hunks.                                        |
| `RepoConfig`                 | Pin a mounted repo to a bookmark or change.                               |
| `DiskCacheConfig`            | Configure on-disk MesaFS cache placement and size.                        |
| `FsStat`                     | File metadata returned by `stat` and `lstat`.                             |
| `ChangeInfo`                 | Change metadata returned by mounted filesystem change operations.         |
| `ExecResult`                 | Output from `fs.bash().exec(...)`.                                        |

Upstream configuration types live in `mesa_sdk.types`:

```python theme={null}
from mesa_sdk.types import TokenAuth, UpstreamConfig, UsernamePasswordAuth

public = UpstreamConfig(url="https://github.com/acme/app.git")
token = UpstreamConfig(
    url="https://github.com/acme/app.git",
    auth=TokenAuth(token="github_pat_...", token_username="bot"),
)
password = UpstreamConfig(
    url="https://git.example.com/acme/app.git",
    auth=UsernamePasswordAuth(username="bot", password="secret"),
)
```

On `mesa.repos.update(...)`, `UpstreamConfig.auth` is tri-state: omit to preserve existing credentials, pass `None` to clear credentials, or pass `TokenAuth` / `UsernamePasswordAuth` to set credentials.

## Error model

REST API operations raise `MesaError` subclasses.

| Exception             | Status       | Meaning                                                               |
| --------------------- | ------------ | --------------------------------------------------------------------- |
| `ValidationError`     | `400`, `406` | Invalid request parameters or unacceptable response variant.          |
| `AuthenticationError` | `401`        | Missing or invalid API key.                                           |
| `AuthorizationError`  | `403`        | The API key does not have the required scope.                         |
| `NotFoundError`       | `404`        | Requested resource does not exist.                                    |
| `ConflictError`       | `409`        | Resource conflict, optimistic concurrency failure, or merge conflict. |
| `RateLimitError`      | `429`        | Rate limit exceeded.                                                  |
| `ServerError`         | `5xx`        | Server-side failure.                                                  |

SDK setup errors include `MissingCredentialError`, `InvalidApiUrlError`, `OrgResolutionError`, and `InvalidOptionsError`.

Filesystem and Bash operations raise built-in Python exceptions such as `FileNotFoundError`, `FileExistsError`, `IsADirectoryError`, `NotADirectoryError`, `PermissionError`, `NotImplementedError`, and `OSError`.

## Raw generated client

`mesa.raw` exposes the authenticated generated `mesa-rest` client. Use it when the high-level SDK does not expose a generated REST operation or option yet.

```python theme={null}
from mesa_rest.api.repo import list_repos

response = await list_repos.asyncio_detailed("acme", client=mesa.raw)
if response.status_code == 200:
    print(response.parsed.repos)
```

Raw generated calls return a `Response[T]` wrapper with `status_code`, `parsed`, and `headers`. High-level SDK methods unwrap successful responses and raise typed errors for non-2xx responses.

## Complete example

```python theme={null}
import asyncio
import base64
from mesa_sdk import Author, FileUpsert, Mesa

mesa = Mesa(org="acme")

async def main():
    repo = await mesa.repos.create(name="demo")

    change = await mesa.changes.create(
        repo=repo.name,
        base_change_id=repo.head_change_id,
        message="Add README",
        author=Author(name="Docs Bot", email="docs@example.com"),
        files=[
            FileUpsert(
                path="README.md",
                content=base64.b64encode(b"# Demo\n").decode(),
            )
        ],
    )

    await mesa.bookmarks.move(
        repo=repo.name,
        bookmark=repo.default_bookmark,
        change_id=change.id,
    )

    async with mesa.fs.mount(repos=[repo.name]) as fs:
        data = await fs.read(f"/{repo.org}/{repo.name}/README.md")
        print(data.decode())

asyncio.run(main())
```
