> ## 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.

# Error Handling

> Handle errors gracefully in your Synap integration.

## Overview

The Synap SDK uses a structured error hierarchy to distinguish between transient errors (which are automatically retried) and permanent errors (which require your intervention). Understanding this hierarchy is essential for building robust integrations. This page is the handling guide: the hierarchy, when each error fires, and the try/except patterns to catch them.

<Card title="Full error reference →" icon="list" href="/sdk-reference/errors">
  For the exhaustive catalog of server-side error codes (the wire-level `code` field, HTTP status, and `details` shape) see [Error Codes](/sdk-reference/errors). For the complete SDK exception table at a glance, see [SDK Reference: Error handling](/sdk-reference/overview#error-handling).
</Card>

## Error Hierarchy

All Synap errors inherit from `SynapError`. The two main branches determine retry behavior:

```
SynapError (base)
├── SynapTransientError (retryable: SDK auto-retries)
│   ├── NetworkTimeoutError
│   ├── RateLimitError
│   ├── ServiceUnavailableError
│   └── AgentUnavailableError
└── SynapPermanentError (non-retryable: requires code/config fix)
    ├── InvalidInputError
    │   ├── InvalidInstanceIdError
    │   └── InvalidConversationIdError
    ├── AuthenticationError
    ├── ContextNotFoundError
    ├── SessionExpiredError
    ├── InsufficientCreditsError
    ├── ListeningAlreadyActiveError
    └── ListeningNotActiveError
```

All errors include an optional `correlation_id` field that uniquely identifies the request. This ID is invaluable for debugging and when contacting Synap support.

<Note>
  The hierarchy below reflects the full public error surface. `AgentUnavailableError` is transient (retryable); all others under `SynapPermanentError` are non-retryable.
</Note>

## Transient Errors

Transient errors represent temporary conditions that typically resolve on their own. The SDK **automatically retries** these errors according to your configured [retry policy](#retry-policy-configuration). You only need to handle them if all retry attempts are exhausted.

### NetworkTimeoutError

<ResponseField name="NetworkTimeoutError" type="SynapTransientError">
  Raised when a network request to Synap Cloud times out before completing.
</ResponseField>

**When it occurs:**

* Network connectivity issues between your application and Synap Cloud
* DNS resolution failures
* The request exceeded the configured `connect` or `read` timeout

**How to handle:**

<Note>
  `conversation_id` must be a valid UUID string; non-UUID values are rejected by the server. Generate one with `str(uuid.uuid4())`, or reuse the UUID you already manage per conversation. The examples below use `str(uuid.uuid4())` to make this explicit.
</Note>

```python theme={null}
import uuid
from maximem_synap import NetworkTimeoutError

try:
    context = await sdk.conversation.context.fetch(
        conversation_id=str(uuid.uuid4()),
        mode="fast"
    )
except NetworkTimeoutError as e:
    logger.warning(
        "Network timeout after all retries (correlation_id=%s)",
        e.correlation_id
    )
    # Fall back to cached context or proceed without memory
    context = None
```

### RateLimitError

<ResponseField name="RateLimitError" type="SynapTransientError">
  Raised when your application exceeds the rate limit for the Synap API. Includes a `retry_after_seconds` field indicating how long to wait before retrying.
</ResponseField>

**When it occurs:**

* Too many requests in a short time window
* Burst traffic exceeding your plan's rate limit

**How to handle:**

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

try:
    response = await sdk.memories.create(
        document=doc,
        document_type="ai-chat-conversation",
        user_id=uid,
        customer_id=cid,
    )
except RateLimitError as e:
    logger.warning(
        "Rate limited. Retry after %d seconds (correlation_id=%s)",
        e.retry_after_seconds,
        e.correlation_id
    )
    # The SDK retries automatically, but if all retries are exhausted:
    # - Queue the operation for later
    # - Reduce request frequency
    # - Consider upgrading your plan
```

<Note>
  The SDK's built-in retry policy respects `retry_after_seconds` automatically. If the rate limit is short (a few seconds), the SDK waits and retries without raising the error to your code. The error only surfaces when all retry attempts are exhausted.
</Note>

### ServiceUnavailableError

<ResponseField name="ServiceUnavailableError" type="SynapTransientError">
  Raised when Synap Cloud is temporarily unavailable due to maintenance, deployment, or an outage.
</ResponseField>

**When it occurs:**

* Synap Cloud is undergoing maintenance
* A rolling deployment is in progress
* Temporary backend issues

**How to handle:**

```python theme={null}
import uuid
from maximem_synap import ServiceUnavailableError

try:
    context = await sdk.conversation.context.fetch(
        conversation_id=str(uuid.uuid4()),
        mode="fast"
    )
except ServiceUnavailableError as e:
    logger.error(
        "Synap Cloud unavailable (correlation_id=%s)",
        e.correlation_id
    )
    # Serve from cache if available, or degrade gracefully
```

## Permanent Errors

Permanent errors indicate problems that will not resolve by retrying. They require changes to your code, configuration, or data.

### InvalidInputError

<ResponseField name="InvalidInputError" type="SynapPermanentError">
  Raised when the request contains invalid parameters or data that fails validation.
</ResponseField>

**When it occurs:**

* Invalid `document_type` value
* Missing required fields
* Parameter values outside valid ranges
* Malformed data in the request body

**How to handle:**

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

try:
    response = await sdk.memories.create(
        document="",                       # Empty document
        document_type="invalid-type",      # Invalid document_type value
        user_id="user_123",
        customer_id="acme_corp",
    )
except InvalidInputError as e:
    logger.error("Invalid input: %s", e)
    # Fix the input data and retry
```

### InvalidInstanceIdError

<ResponseField name="InvalidInstanceIdError" type="InvalidInputError">
  A defined subtype of `InvalidInputError` for an unknown or malformed `instance_id`.
</ResponseField>

<Note>
  The SDK currently surfaces a bad `instance_id` as the base **`InvalidInputError`** (HTTP 400), not as this specific subtype. Catch `InvalidInputError` to handle it; that also catches this subtype if a future SDK version raises it directly.
</Note>

**When it occurs:**

* The `instance_id` does not match the expected format
* The instance has been deleted or deactivated
* A typo in the instance ID

**How to handle:**

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

try:
    await sdk.initialize()
except InvalidInputError as e:
    logger.error(
        "Invalid instance ID. Verify in the Synap Dashboard. "
        "(correlation_id=%s)", e.correlation_id
    )
    raise
```

### InvalidConversationIdError

<ResponseField name="InvalidConversationIdError" type="InvalidInputError">
  A defined subtype of `InvalidInputError` for a malformed `conversation_id` (for example, a non-UUID string).
</ResponseField>

<Note>
  The SDK currently surfaces a malformed `conversation_id` as the base **`InvalidInputError`** (HTTP 400), not as this specific subtype. Catch `InvalidInputError`. Note that a *well-formed* `conversation_id` with no messages yet does **not** raise; it returns an empty `ContextResponse` (see [cold-start behavior](#contextnotfounderror)).
</Note>

**When it occurs:**

* The `conversation_id` is not a valid UUID
* A typo or wrong identifier format

**How to handle:**

```python theme={null}
import uuid
from maximem_synap import InvalidInputError

try:
    context = await sdk.conversation.context.fetch(
        conversation_id=str(uuid.uuid4())  # must be a valid UUID
    )
except InvalidInputError as e:
    logger.warning("Malformed conversation_id: %s", e)
    # Generate a valid UUID and retry
```

### AuthenticationError

<ResponseField name="AuthenticationError" type="SynapPermanentError">
  Raised when the SDK cannot authenticate with Synap Cloud. This is a general authentication failure.
</ResponseField>

**When it occurs:**

* API key is invalid or revoked
* No API key was provided (neither `SYNAP_API_KEY` env var nor the `api_key=` constructor argument)
* The instance's credentials have been rotated without updating the API key your application uses

**How to handle:**

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

try:
    await sdk.initialize()
except AuthenticationError as e:
    logger.error(
        "Authentication failed: %s (correlation_id=%s)",
        e, e.correlation_id
    )
    # Re-bootstrap with a new token, or check credential files
```

### ContextNotFoundError

<ResponseField name="ContextNotFoundError" type="SynapPermanentError">
  Raised when a requested context resource genuinely cannot be located, distinct from a valid resource that simply has no memories yet, which returns an **empty** `ContextResponse` rather than raising.
</ResponseField>

**Empty result vs. raised error, which happens per method:**

* `sdk.conversation.context.fetch()`: a brand-new or never-ingested `conversation_id` returns an **empty** `ContextResponse` (`facts == []`, `preferences == []`, etc.), **not** an error. This is the normal cold-start path. A malformed (non-UUID) `conversation_id` raises `InvalidInputError` instead.
* `sdk.user.context.fetch()` / `sdk.customer.context.fetch()` / `sdk.client.context.fetch()`: a scope that has never had memories ingested also returns an **empty** `ContextResponse`. Treat empty lists as "no context yet," not as an error.

`ContextNotFoundError` is reserved for the case where the underlying context resource itself is missing or was removed, not for the everyday "new conversation / new user" case.

**When it occurs:**

* A previously available context resource was deleted before retrieval
* The backend cannot locate the addressed context resource (as opposed to locating it and finding it empty)

**How to handle:**

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

try:
    context = await sdk.user.context.fetch(user_id="user_12345")
except ContextNotFoundError:
    logger.info("Context resource missing for user, starting with empty state")
    context = None

# Note: a user who simply has no memories yet does NOT raise here; the fetch
# returns an empty ContextResponse. Check `not context.facts and not
# context.preferences and ...` for the cold-start case rather than relying on
# this exception.
```

### SessionExpiredError

<ResponseField name="SessionExpiredError" type="SynapPermanentError">
  Raised when the current session has expired and cannot be resumed. Sessions are time-bounded and must be re-established after expiry.
</ResponseField>

**When it occurs:**

* The session has been idle beyond its expiry window
* The session was invalidated server-side (e.g., credential rotation)

**How to handle:**

```python theme={null}
import uuid
from maximem_synap import SessionExpiredError

try:
    context = await sdk.conversation.context.fetch(
        conversation_id=str(uuid.uuid4())
    )
except SessionExpiredError as e:
    logger.warning(
        "Session expired (correlation_id=%s). Re-initializing...",
        e.correlation_id
    )
    await sdk.initialize()
    # Retry the operation
```

### InsufficientCreditsError

<ResponseField name="InsufficientCreditsError" type="SynapPermanentError">
  Raised when the client's credit balance is too low to satisfy the request. Carries `balance_credits`, `minimum_required_credits`, `recovery_url`, and `redeem_url` so you can surface the right next-step to the user. See [Pricing & Credits](/resources/pricing) for how credits and overage work.
</ResponseField>

**When it occurs:**

* The client has run out of credits
* The current credit balance is below the minimum required for the requested operation

**How to handle:**

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

try:
    response = await sdk.memories.create(
        document=conversation_text,
        document_type="ai-chat-conversation",
        user_id=user_id,
        customer_id=customer_id,
    )
except InsufficientCreditsError as e:
    logger.error(
        "Insufficient credits: balance=%s, required=%s, recover=%s",
        e.balance_credits, e.minimum_required_credits, e.recovery_url
    )
    # Surface the recovery / redeem URL to the operator or user
```

### AgentUnavailableError

<ResponseField name="AgentUnavailableError" type="SynapTransientError">
  Raised when the Synap agent backing the instance is temporarily unavailable. This is a transient error; the SDK will automatically retry.
</ResponseField>

**When it occurs:**

* The agent process is restarting or being redeployed
* Temporary resource contention on the backend

**How to handle:**

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

try:
    response = await sdk.memories.create(
        document=conversation_text,
        user_id=user_id,
        customer_id=customer_id
    )
except AgentUnavailableError as e:
    logger.warning(
        "Agent unavailable after all retries (correlation_id=%s)",
        e.correlation_id
    )
    # Queue for retry later
```

<Note>
  The SDK automatically retries `AgentUnavailableError` according to your configured retry policy. This error only surfaces to your code when all retry attempts are exhausted.
</Note>

### ListeningAlreadyActiveError

<ResponseField name="ListeningAlreadyActiveError" type="SynapPermanentError">
  Raised when you call `listen()` on an instance that already has an active listening stream. Only one stream can be active per SDK instance at a time.
</ResponseField>

**When it occurs:**

* Calling `listen()` a second time without first calling `stop_listening()`
* Duplicate initialization paths in your application

**How to handle:**

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

try:
    await sdk.instance.listen(on_context=on_event)
except ListeningAlreadyActiveError:
    logger.warning("Listening stream already active; skipping duplicate call")
    # No action needed; the existing stream is still running
```

### ListeningNotActiveError

<ResponseField name="ListeningNotActiveError" type="SynapPermanentError">
  Raised when you call `stop_listening()` or another stream operation when no listening stream is currently active.
</ResponseField>

**When it occurs:**

* Calling `stop_listening()` before `listen()` has been called
* Calling stream operations after the stream has already been stopped

**How to handle:**

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

try:
    await sdk.instance.stop_listening()
except ListeningNotActiveError:
    logger.info("No active stream to stop; already inactive")
    # Safe to ignore
```

## Using correlation\_id

Every Synap error includes an optional `correlation_id` that uniquely identifies the failed request within Synap's distributed tracing system.

```python theme={null}
import uuid
from maximem_synap import SynapError

try:
    context = await sdk.conversation.context.fetch(
        conversation_id=str(uuid.uuid4())
    )
except SynapError as e:
    logger.error(
        "Synap error: %s | correlation_id: %s | type: %s",
        str(e),
        e.correlation_id,
        type(e).__name__
    )
```

<Tip>
  Always log the `correlation_id` from errors. Synap support can use it to trace the exact request path through the backend, including which pipeline stages were involved, what data was processed, and where the failure occurred.
</Tip>

## Retry Policy Configuration

The SDK's built-in retry policy handles transient errors automatically. You can customize the retry behavior through `RetryPolicy` in your `SDKConfig`.

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

config = SDKConfig(
    retry_policy=RetryPolicy(
        max_attempts=5,               # Total attempts (1 initial + 4 retries)
        backoff_base=1.0,             # Base delay in seconds
        backoff_max=10.0,             # Maximum delay between retries
        backoff_jitter=True,          # Add random jitter to prevent thundering herd
        retryable_errors=[            # Error types to retry
            "NetworkTimeoutError",
            "RateLimitError",
            "ServiceUnavailableError",
            "SynapTransientError"
        ]
    )
)
```

### Retry Behavior

The SDK uses exponential backoff with optional jitter:

```
Attempt 1: Immediate
Attempt 2: backoff_base * 2^0 = 1.0s  (± jitter)
Attempt 3: backoff_base * 2^1 = 2.0s  (± jitter)
Attempt 4: backoff_base * 2^2 = 4.0s  (± jitter)
Attempt 5: backoff_base * 2^3 = 8.0s  (± jitter, capped at backoff_max)
```

For `RateLimitError`, the SDK respects the `retry_after_seconds` value instead of the exponential backoff, waiting the exact duration specified by the server.

### Disabling Retries

To disable automatic retries entirely (useful for testing or when you implement your own retry logic):

```python theme={null}
config = SDKConfig(retry_policy=None)
```

### Customizing Retryable Errors

By default, all four transient error types are retried (`NetworkTimeoutError`, `RateLimitError`, `ServiceUnavailableError`, and `AgentUnavailableError`). Listing the base `SynapTransientError` in `retryable_errors` covers every transient subtype, including any added in future SDK releases. You can customize this list, though adding permanent errors is generally not recommended.

```python theme={null}
config = SDKConfig(
    retry_policy=RetryPolicy(
        retryable_errors=[
            "NetworkTimeoutError",
            "ServiceUnavailableError"
            # Removed RateLimitError, handle rate limits manually
        ]
    )
)
```

## Common Error Handling Patterns

### Catch-All with Transient/Permanent Distinction

```python theme={null}
from maximem_synap import (
    SynapError,
    SynapTransientError,
    SynapPermanentError,
)

try:
    response = await sdk.memories.create(
        document=conversation_text,
        document_type="ai-chat-conversation",
        user_id=user_id,
        customer_id=customer_id,
        mode="long-range",
    )
except SynapTransientError as e:
    # All retries exhausted for a transient error
    logger.warning(
        "Transient error after retries: %s (correlation_id=%s)",
        e, e.correlation_id
    )
    # Queue for retry later, or degrade gracefully
    await retry_queue.enqueue(conversation_text, user_id)

except SynapPermanentError as e:
    # Something is fundamentally wrong
    logger.error(
        "Permanent error: %s (correlation_id=%s)",
        e, e.correlation_id
    )
    # Alert, fix the issue, do not retry as-is
    raise

except SynapError as e:
    # Catch-all for any unexpected Synap error
    logger.error("Unexpected Synap error: %s", e)
    raise
```

### Per-Operation Error Handling

```python theme={null}
from maximem_synap import (
    InvalidInputError,
    NetworkTimeoutError,
    RateLimitError,
    SynapError,
)

async def safe_fetch_context(conversation_id: str, query: str):
    """Fetch context with graceful degradation."""
    try:
        return await sdk.conversation.context.fetch(
            conversation_id=conversation_id,
            search_query=[query],
            mode="fast"
        )

    except InvalidInputError:
        logger.info("Malformed conversation_id %s, skipping memory", conversation_id)
        return None

    except NetworkTimeoutError:
        logger.warning("Timeout fetching context, proceeding without memory")
        return None

    except RateLimitError as e:
        logger.warning(
            "Rate limited, retry after %ds", e.retry_after_seconds
        )
        return None

    except SynapError as e:
        logger.error(
            "Unexpected error fetching context: %s (correlation_id=%s)",
            e, e.correlation_id
        )
        return None
```

### Initialization Error Handling

```python theme={null}
from maximem_synap import (
    AuthenticationError,
    InvalidInputError,
    NetworkTimeoutError,
    SynapError,
)

async def initialize_with_fallback():
    """Initialize SDK with comprehensive error handling."""
    try:
        await sdk.initialize()
        return True

    except InvalidInputError:
        logger.error("Invalid instance ID. Check your configuration.")
        return False

    except AuthenticationError as e:
        logger.error(
            "Auth failed (no API key or invalid credentials): %s (correlation_id=%s)",
            e, e.correlation_id
        )
        return False

    except NetworkTimeoutError:
        logger.error("Cannot reach Synap Cloud. Check network connectivity.")
        return False

    except SynapError as e:
        logger.error(
            "Unexpected initialization error: %s (correlation_id=%s)",
            e, e.correlation_id
        )
        return False
```

## Full error reference

The hierarchy and handling patterns above cover how to catch and respond to each error. For the at-a-glance catalog (every SDK exception class with its transient/permanent type and common cause) see [SDK Reference: Error handling](/sdk-reference/overview#error-handling). For the server-side wire codes those exceptions wrap (HTTP status, machine-readable `code`, and `details` shape) see [Error Codes](/sdk-reference/errors).

## Next Steps

<CardGroup cols={2}>
  <Card title="SDK Configuration" icon="sliders" href="/sdk/configuration">
    Customize retry policies, timeouts, and other SDK settings.
  </Card>

  <Card title="Initializing the SDK" icon="play" href="/sdk/initialization">
    Set up the SDK with proper error handling from the start.
  </Card>

  <Card title="Support" icon="life-ring" href="/resources/support">
    Contact Synap support with correlation IDs for issue resolution.
  </Card>

  <Card title="FAQ" icon="circle-question" href="/resources/faq">
    Common questions about errors and troubleshooting.
  </Card>
</CardGroup>
