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

# Initializing the SDK

> Set up the Synap SDK in your application.

## Overview

Before your application can ingest memories or retrieve context, you must initialize the Synap SDK. Initialization validates your API key, establishes a secure connection, and prepares local caching. The SDK follows a strict **initialize, use, shutdown** lifecycle.

## Basic Initialization

The simplest way to get started requires only your `SYNAP_API_KEY` environment variable. Generate an API key from the [Synap Dashboard](https://synap.maximem.ai) and set it in your environment.

```python theme={null}
from maximem_synap import MaximemSynapSDK

sdk = MaximemSynapSDK(
    api_key="synap_your_key_here"
)

await sdk.initialize()
```

<Warning>
  You **must** call `await sdk.initialize()` before invoking any SDK operations. Calling methods like `sdk.memories.create()` before initialization raises an `AuthenticationError`.
</Warning>

### What `initialize()` Does

When you call `initialize()`, the SDK performs the following steps in order:

<Steps>
  <Step title="Resolve API Key">
    The SDK picks up the API key from the `api_key=` argument (if passed) or the `SYNAP_API_KEY` environment variable.
  </Step>

  <Step title="Resolve Identity">
    The SDK asks Synap who the key belongs to. Your API key is the authoritative identity: this is why you never have to plumb an instance or client ID through your application yourself.
  </Step>

  <Step title="Connection Establishment">
    The SDK opens an authenticated connection to Synap and, if real-time streaming is enabled, an additional streaming channel.
  </Step>

  <Step title="Cache Initialization">
    If a `cache_backend` is configured (default: `sqlite`), the SDK sets up the local cache database at the storage path, namespaced to the client the key resolved to.
  </Step>
</Steps>

<Note>
  Identity is resolved here, at `initialize()`, not when you construct the SDK. Construction only records which API key this SDK will use, and that key is what decides whether you get a new SDK or the existing one for that credential (see [Singleton Pattern](#singleton-pattern)).
</Note>

## Initialization with Custom Configuration

Pass an `SDKConfig` object to customize SDK behavior at construction time.

```python theme={null}
from maximem_synap import MaximemSynapSDK, SDKConfig, TimeoutConfig, RetryPolicy

config = SDKConfig(
    storage_path="/var/lib/myapp/synap",
    cache_backend="sqlite",
    session_timeout_minutes=60,
    timeouts=TimeoutConfig(
        connect=10.0,
        read=45.0,
        write=15.0,
        stream_idle=120.0
    ),
    retry_policy=RetryPolicy(
        max_attempts=5,
        backoff_base=1.5,
        backoff_max=30.0,
        backoff_jitter=True
    ),
    log_level="INFO"
)

sdk = MaximemSynapSDK(
    api_key="synap_your_key_here",
    config=config
)

await sdk.initialize()
```

See [SDK Configuration](/sdk/configuration) for a complete reference of all configuration options. For the full `initialize()` signature and parameters, see the [API Reference](/sdk-reference/lifecycle/initialize).

## Singleton Pattern

The SDK keeps **one live instance per API key**. Construct it twice with the same key (from two modules, or on every request) and the second construction hands you the SDK that already exists instead of opening a second set of connections and caches.

```python theme={null}
sdk_a = MaximemSynapSDK(api_key="synap_key_1")
sdk_b = MaximemSynapSDK(api_key="synap_key_1")

# Same key, one live SDK: sdk_b shares sdk_a's connections, caches and credentials.
```

This prevents accidental duplication of connections and caches in applications that construct the SDK from multiple modules.

<Note>
  `sdk_a is sdk_b` evaluates to `False`. The two objects share their internal state; they are not literally the same object. Use the SDK, don't identity-check it.
</Note>

<Note>
  **`SYNAP_INSTANCE_ID` in the environment is fine. `instance_id=` in the constructor is not.** They behave differently on purpose.

  The environment variable records which instance you are on and leaves the SDK keyed on your credential, so two keys stay independent. The constructor argument makes the id the identity: a second key used under it is silently discarded, and rotating a key that way has no effect. The dashboard gives you both variables to paste, and your API key already determines which instance you reach either way, so there is nothing to gain from the constructor form.
</Note>

### Running multiple API keys in one process

Construct the SDK with **different** API keys and you get independent SDKs, each with its own credentials, connections, caches and short-term stores. This is the supported pattern for a backend serving several tenants, and for pointing one SDK at staging and another at production inside a single process.

```python theme={null}
tenant_a = MaximemSynapSDK(api_key="synap_key_tenant_a")
tenant_b = MaximemSynapSDK(api_key="synap_key_tenant_b")

await tenant_a.initialize()
await tenant_b.initialize()

# Each SDK authenticates as its own tenant. Local caches are namespaced per
# instance under ~/.synap/<client_id>/<instance_id>/, so they don't collide
# either.
```

<Warning>
  The per-instance segment of that path arrived in **0.4.3**. Up to 0.4.2 the cache was namespaced by `client_id` alone: the Synap *account*, not the memory store, so two instances belonging to one account shared the same cache files. Where the same `customer_id` or `user_id` appeared under both, one instance could be served the other's cached context. Server-side scoping was never affected; this was local disk only. Upgrading moves the cache to the new path, so the first request per entity after the upgrade is a miss.
</Warning>

<Warning>
  **Requires `maximem-synap` 0.4.1 or newer.** In earlier versions the singleton was keyed on the instance ID, which is empty at construction time and only resolved from your API key during `initialize()`. Every SDK built without an explicit `instance_id` therefore landed in the same slot, and the second construction silently adopted the first one's credentials, so the second tenant's reads and writes were executed against the first tenant's instance, with no error raised.

  If your process constructs SDKs for more than one API key, upgrade before relying on this section:

  ```bash theme={null}
  pip install --upgrade "maximem-synap>=0.4.1"
  ```
</Warning>

From 0.4.2 this also holds once an SDK knows its own instance. `initialize()` resolves the instance from your API key, and the SDK answers to that instance ID from then on, so constructing with it returns the same live SDK rather than standing up a second set of connections, caches and streams for one instance.

#### Two keys, one instance

The one case that gives you duplication rather than reuse is **two different API keys issued against the same instance**. Each key is its own identity, so each gets its own SDK: two Listen streams, two anticipation caches and two short-term stores, all for one instance.

That is deliberate. Merging them would mean one caller transacting on the other's credential, which would make key rotation, revocation and per-key attribution all silently wrong. The cost is that short-term context recorded through one SDK is not visible to the other until the server round-trips it, and that the pair consumes two of the instance's concurrent streams.

`initialize()` logs a warning when it detects this, naming the instance, so it does not stay invisible. Unless you specifically want two separately-authenticated SDKs, use one API key per instance per process.

### Rotating a key in a long-running process

An SDK keeps the credential it was constructed with for its whole life, so rolling a new key into your secrets manager does not by itself change what a running process authenticates as. What happens next depends on how you construct:

| How you construct                  | Passing a new key                                                                                                    |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `MaximemSynapSDK(api_key=...)`     | A different key is a different identity, so you get a new SDK on the new key. The rotation takes effect immediately. |
| `MaximemSynapSDK(instance_id=...)` | The instance ID is the identity, so you get the **existing** SDK back: still on the old key.                         |

If you construct by `instance_id`, call `await sdk.shutdown()` before reconstructing (that releases the slot, so the next construction builds a fresh SDK on the new key), or restart the worker. Do it **before** revoking the old key, or in-flight requests start failing authentication.

<Note>
  Two keys issued against the same instance are one identity only on the `instance_id` path. Constructed the usual way, with `api_key=`, each key gets its own SDK authenticating as itself, which is why rotation on that path needs nothing special.
</Note>

### Overriding the Singleton for Testing

In test environments, use `_force_new=True` to bypass the singleton entirely and build a fresh SDK on every construction, even for a key that already has one.

```python theme={null}
sdk = MaximemSynapSDK(
    api_key="synap_test_key",
    _force_new=True
)
```

An SDK built this way is never registered as the singleton for its key. It is therefore invisible to other constructions, and shutting it down leaves any live SDK for the same key untouched, which is what makes it safe to create and discard one per test.

<Note>
  `_force_new` is intended for tests and for framework adapters that manage SDK lifetime themselves. You do not need it to run several tenants in one process, because different API keys already give you separate SDKs. Reaching for it in application code means opting out of connection and cache reuse, so each extra SDK pays for its own connections, its own streaming channel, and its own cache handles.
</Note>

## Environment Variable Initialization

For CI/CD, containers, serverless, and production, just set the environment variable:

<CodeGroup>
  ```bash Linux / macOS theme={null}
  export SYNAP_API_KEY="synap_your_key_here"
  export SYNAP_INSTANCE_ID="inst_your_instance_id"
  ```

  ```powershell Windows (PowerShell, session) theme={null}
  $env:SYNAP_API_KEY = "synap_your_key_here"
  $env:SYNAP_INSTANCE_ID = "inst_your_instance_id"
  ```

  ```powershell Windows (PowerShell, persistent) theme={null}
  [System.Environment]::SetEnvironmentVariable("SYNAP_API_KEY", "synap_your_key_here", "User")
  [System.Environment]::SetEnvironmentVariable("SYNAP_INSTANCE_ID", "inst_your_instance_id", "User")
  ```

  ```ini .env file (with python-dotenv) theme={null}
  SYNAP_API_KEY=synap_your_key_here
  SYNAP_INSTANCE_ID=inst_your_instance_id
  ```
</CodeGroup>

```python theme={null}
from maximem_synap import MaximemSynapSDK

sdk = MaximemSynapSDK()
await sdk.initialize()
```

The SDK reads `SYNAP_API_KEY` automatically and resolves the instance ID from the server.

## The `configure()` Method

If you need to adjust configuration after constructing the SDK but before calling `initialize()`, use the `configure()` method.

```python theme={null}
sdk = MaximemSynapSDK(
    api_key="synap_your_key_here"
)

# Adjust config before initialization
sdk.configure(
    log_level="DEBUG",
    session_timeout_minutes=120
)

await sdk.initialize()
```

<Warning>
  Calling `configure()` after `initialize()` raises `InvalidInputError('Cannot reconfigure after initialization')`. All configuration must be finalized before the SDK is initialized.
</Warning>

## SDK Lifecycle

The SDK follows a strict three-phase lifecycle:

<Frame>
  <img src="https://mintcdn.com/maximemai/-72Xx_Hb5rRx2NQf/images/sdk-lifecycle.png?fit=max&auto=format&n=-72Xx_Hb5rRx2NQf&q=85&s=032fcc9b182a69a6b61c8de172aa5054" alt="SDK lifecycle: initialize, use, shutdown" width="1536" height="1024" data-path="images/sdk-lifecycle.png" />
</Frame>

### 1. Initialize

Call `await sdk.initialize()` to validate the API key and establish connections.

### 2. Use

Invoke SDK operations: `sdk.memories.*`, `sdk.conversation.context.*`, `sdk.cache.*`, etc. All operations are async and must be awaited.

### 3. Shutdown

Call `await sdk.shutdown()` to gracefully tear down the SDK.

```python theme={null}
await sdk.shutdown()
```

<Warning>
  Always call `shutdown()` before your application exits to ensure pending telemetry is flushed and active streaming connections are closed cleanly. Failing to call `shutdown()` may result in lost telemetry data and lingering connections.
</Warning>

## Initialize and Shut Down Cleanly

For cleaner lifecycle management, wrap your application logic in a `try/finally` so `shutdown()` always runs, even if an error is raised mid-flight.

```python theme={null}
from maximem_synap import MaximemSynapSDK

sdk = MaximemSynapSDK(
    api_key="synap_your_key_here"
)

try:
    await sdk.initialize()

    # Your application logic
    response = await sdk.memories.create(
        document="User asked about project deadlines...",
        document_type="ai-chat-conversation",
        user_id="user_12345",
        customer_id="acme_corp",
    )
    print(f"Ingestion ID: {response.ingestion_id}")

finally:
    await sdk.shutdown()
```

## Full Example with Error Handling

The following example demonstrates a production-ready initialization pattern with comprehensive error handling.

```python theme={null}
import logging
import uuid
from maximem_synap import MaximemSynapSDK, SDKConfig, TimeoutConfig, RetryPolicy
from maximem_synap import (
    AuthenticationError,
    NetworkTimeoutError,
    SynapError,
)

logger = logging.getLogger(__name__)


async def create_synap_sdk() -> MaximemSynapSDK:
    """Initialize the Synap SDK with production-ready configuration."""
    config = SDKConfig(
        storage_path="/var/lib/myapp/synap",
        cache_backend="sqlite",
        session_timeout_minutes=60,
        timeouts=TimeoutConfig(connect=10.0, read=30.0),
        retry_policy=RetryPolicy(max_attempts=3),
        log_level="WARNING",
    )

    sdk = MaximemSynapSDK(
        api_key="synap_your_key_here",
        config=config,
    )

    try:
        await sdk.initialize()
        logger.info("Synap SDK initialized successfully")
        return sdk

    except AuthenticationError as e:
        logger.error(
            "Authentication failed: %s (correlation_id=%s)",
            e, e.correlation_id
        )
        raise

    except NetworkTimeoutError:
        logger.error(
            "Could not reach Synap Cloud. Check network connectivity."
        )
        raise

    except SynapError as e:
        logger.error(
            "Unexpected Synap error during init: %s (correlation_id=%s)",
            e, e.correlation_id
        )
        raise


async def main():
    sdk = await create_synap_sdk()
    try:
        # Application logic here.
        # conversation_id must be a valid UUID string; generate one with
        # str(uuid.uuid4()) or reuse a UUID you already manage per conversation.
        context = await sdk.conversation.context.fetch(
            conversation_id=str(uuid.uuid4()),
            mode="fast"
        )
        print(f"Retrieved {len(context.facts)} facts")
    finally:
        await sdk.shutdown()
        logger.info("Synap SDK shut down cleanly")
```

<Tip>
  The API key is read fresh on every start. There is no one-time setup step: the same `SYNAP_API_KEY` works forever (until you revoke it in the Dashboard).
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="Ingesting Memories" icon="upload" href="/sdk/ingestion">
    Send conversations and documents into Synap's memory system.
  </Card>

  <Card title="Retrieving Memories" icon="search" href="/sdk/context-fetch">
    Query contextual memories for your AI agent.
  </Card>

  <Card title="SDK Configuration" icon="sliders" href="/sdk/configuration">
    Explore all configuration options in detail.
  </Card>
</CardGroup>
