Metadata-Version: 2.5
Name: memorysync
Version: 1.9.2
Summary: Official Python client for the MemorySync API.
Project-URL: Homepage, https://memorysync.io
Project-URL: Documentation, https://docs.memorysync.io
Project-URL: API Reference, https://docs.memorysync.io/api/overview
Project-URL: Quickstart, https://docs.memorysync.io/sdks/python/overview
Project-URL: Changelog, https://docs.memorysync.io/release-notes
Project-URL: Support, https://docs.memorysync.io/debugging/support
Project-URL: Status, https://status.memorysync.io
Author: MemorySync
License: MIT
License-File: LICENSE
Keywords: agent-memory,ai,ai-agents,client,context,llm,long-term-memory,memory,memorysync,rag,retrieval,sdk
Classifier: Development Status :: 5 - Production/Stable
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
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1.0,>=0.25
Requires-Dist: typing-extensions>=4.5; python_version < '3.11'
Description-Content-Type: text/markdown

# memorysync

Official Python client for the MemorySync API. Sync and async, no surprises.

```bash
pip install memorysync
```

## Quick start

```python
from memorysync import MemorySyncClient

ms = MemorySyncClient(
    api_key="...",
    base_url="https://api.memorysync.io",
    project_id="proj_xxxxxxxxxxxxxxxx",   # optional
    end_user_id="user_42",                 # optional
)

ms.add("User prefers dark mode.")

result = ms.query("ui preferences", k=5)
for m in result.memories:
    print(m.id, m.text)
```

## Async usage

```python
import asyncio
from memorysync import AsyncMemorySyncClient

async def main():
    async with AsyncMemorySyncClient(api_key="...", base_url="...") as ms:
        result = await ms.query("ui preferences", k=5)
        print(result.memories)

asyncio.run(main())
```

Use `MemorySyncClient` as a context manager when you want deterministic
connection cleanup:

```python
with MemorySyncClient(api_key="...", base_url="...") as ms:
    ms.add("...")
```

## Configuration

| Argument        | Required | Description                                                                                  |
| --------------- | -------- | -------------------------------------------------------------------------------------------- |
| `api_key`       | yes      | Sent as `X-API-Key`. Provision in your MemorySync dashboard.                                 |
| `base_url`      | yes      | Deployment URL of your MemorySync instance.                                                  |
| `project_id`    | no       | Pin every request to a project (`X-Project-ID`). Format: `proj_` + 16 hex chars.             |
| `end_user_id`   | no       | Identify which of *your* users this client speaks for (`X-End-User-ID`).                     |
| `timeout`       | no       | Per-request timeout in seconds. Default `30.0`.                                              |
| `transport`     | no       | Inject a custom `httpx` transport (tests, retries, proxies).                                 |

`end_user_id` can also be passed per-call on `add()` to override the client default.

## Methods

Every method maps 1:1 to a real HTTP route. The two clients share the same
surface; only the call style differs (sync vs `await`).

| Method                                       | Route                                  |
| -------------------------------------------- | -------------------------------------- |
| `add(text, **opts)`                          | `POST /memory/add`                     |
| `bulk_add(items)`                            | `POST /memory/bulk-add`                |
| `query(query, *, k=None, ...)`               | `POST /memory/query`                   |
| `get(memory_id)`                             | `GET /memory/{id}`                     |
| `update(memory_id, **fields)`                | `PATCH /memory/{id}`                   |
| `forget(memory_ids, *, reason=None)`         | `DELETE /memory/forget`                |
| `summarize(memory_ids, *, lossless=False)`   | `POST /memory/summarize`               |
| `compose(prompt_template, *, recall_k=None)` | `POST /memory/compose`                 |
| `export_all()`                               | `GET /memory/export`                   |
| `create_relation(from_id, **opts)`           | `POST /memory/{id}/relations`          |

### `add` returns one of two shapes

`add()` runs through MemorySync's extraction pipeline, so input that carries no
high-value content is intentionally skipped. Branch on the type of the result:

```python
from memorysync import AddSkippedResponse, Memory

result = ms.add("User prefers dark mode.")
if isinstance(result, AddSkippedResponse):
    print("skipped:", result.reason)
else:
    assert isinstance(result, Memory)
    print(result.id, result.text)
```

## Control-plane client

Dashboard and administrative routes use bearer authentication, not the memory
client's API key. Tokens are never persisted or refreshed automatically.
Responses are typed dictionaries with snake_case keys, including routes whose
wire response uses camelCase.

```python
import os
from memorysync import ControlPlaneClient

with ControlPlaneClient(
    "https://api.memorysync.io",
    access_token=os.environ["MEMORYSYNC_ACCESS_TOKEN"],
    project_id="project_abc123",  # optional X-Project-ID default
) as control:
    members = control.list_team_members()
    hooks = control.list_webhooks()

# Login is the only operation that does not require a configured token.
with ControlPlaneClient("https://api.memorysync.io") as control:
    login = control.login("developer@example.com", os.environ["MEMORYSYNC_PASSWORD"])
```

`AsyncControlPlaneClient` exposes the same methods with `await` and `aclose()`.
Both clients accept `base_url`, optional `access_token`, optional `project_id`,
`timeout`, and an injectable sync/async `httpx` `transport`. Project-scoped
operations accept a per-call `project_id` override.

| Method | HTTP route |
| --- | --- |
| `bulk_revoke_api_keys` | `POST /org/api-keys/bulk-revoke` |
| `test_api_key` | `POST /org/api-keys/{key_id}/test` |
| `login` | `POST /auth/login` |
| `get_current_plan` | `GET /org/billing/current-plan` |
| `list_team_members` | `GET /admin/team/members` |
| `suspend_team_member` | `PATCH /admin/team/members/{member_id}` |
| `remove_team_member` | `DELETE /admin/team/members/{member_id}` |
| `list_sessions` | `GET /auth/sessions` |
| `revoke_session` | `POST /auth/sessions/{session_id}/revoke` |
| `list_audit_events` | `GET /admin/audit-logs` |
| `list_integrations` | `GET /api/v1/integrations/catalog` |
| `create_organization` | `POST /organizations` |
| `list_organizations` | `GET /organizations` |
| `list_organization_members` | delegates to `list_team_members` |
| `get_organization_settings` | `GET /admin/tenant-settings` |
| `list_projects` | `GET /org/projects` |
| `create_webhook` | `POST /org/webhooks` |
| `list_webhooks` | `GET /org/webhooks` |
| `update_webhook` | `PATCH /org/webhooks/{endpoint_id}` |
| `delete_webhook` | `DELETE /org/webhooks/{endpoint_id}` |
| `test_webhook` | `POST /org/webhooks/{endpoint_id}/test` |
| `replay_webhook_deliveries` | `POST /org/webhooks/{endpoint_id}/replay` |
| `list_webhook_deliveries` | `GET /org/webhooks/{endpoint_id}/deliveries` |

## Errors

Every non-2xx response raises a typed subclass of `MemorySyncError`:

| Class             | When                                        |
| ----------------- | ------------------------------------------- |
| `AuthError`       | `401` / `403` — bad key, missing scope.     |
| `ValidationError` | `400` / `409` / `422`.                      |
| `NotFoundError`   | `404` — record not visible to the caller.   |
| `RateLimitError`  | `429` — read `err.retry_after_seconds`.     |
| `ServerError`     | `5xx`.                                      |
| `MemorySyncError` | Network errors, timeouts, anything else.    |

Every error carries `status_code`, `response`, and the server-issued
`request_id` (when present) for support escalation.

```python
import time
from memorysync import RateLimitError

try:
    ms.add("...")
except RateLimitError as e:
    time.sleep(e.retry_after_seconds)
```

## License

MIT
